From 19c6760ea7f6f8a1a6fe6c80c42d5ddbf511927e Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 1 Dec 2010 18:57:05 +0000 Subject: [PATCH 01/67] * I2CP: Change from the internal pseudo-socket that was implemented in 0.7.9 to an internal Queue that directly passes I2CPMessage objects. For in-JVM clients, this eliminates two writer threads per client and avoids the serialization/deserialization of I2CP messages. --- core/java/src/net/i2p/I2PAppContext.java | 10 +++ .../net/i2p/client/ClientWriterRunner.java | 19 +--- .../src/net/i2p/client/I2PSessionImpl.java | 57 ++++++++---- .../src/net/i2p/client/I2PSimpleSession.java | 31 +++++-- .../net/i2p/data/i2cp/I2CPMessageReader.java | 22 +++-- .../net/i2p/internal/I2CPMessageQueue.java | 51 +++++++++++ .../i2p/internal/InternalClientManager.java | 19 ++++ .../net/i2p/internal/PoisonI2CPMessage.java | 58 ++++++++++++ .../i2p/internal/QueuedI2CPMessageReader.java | 55 ++++++++++++ core/java/src/net/i2p/internal/package.html | 7 ++ .../src/net/i2p/router/RouterContext.java | 22 +++-- .../router/client/ClientConnectionRunner.java | 18 +--- .../net/i2p/router/client/ClientManager.java | 28 ++++-- .../client/ClientManagerFacadeImpl.java | 17 +++- .../i2p/router/client/ClientWriterRunner.java | 19 +--- .../router/client/I2CPMessageQueueImpl.java | 57 ++++++++++++ .../client/InternalClientListenerRunner.java | 89 ------------------- .../client/QueuedClientConnectionRunner.java | 76 ++++++++++++++++ 18 files changed, 476 insertions(+), 179 deletions(-) create mode 100644 core/java/src/net/i2p/internal/I2CPMessageQueue.java create mode 100644 core/java/src/net/i2p/internal/InternalClientManager.java create mode 100644 core/java/src/net/i2p/internal/PoisonI2CPMessage.java create mode 100644 core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java create mode 100644 core/java/src/net/i2p/internal/package.html create mode 100644 router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java delete mode 100644 router/java/src/net/i2p/router/client/InternalClientListenerRunner.java create mode 100644 router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index b49779640..458aee31e 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -22,6 +22,7 @@ import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TransientSessionKeyManager; import net.i2p.data.RoutingKeyGenerator; +import net.i2p.internal.InternalClientManager; import net.i2p.stat.StatManager; import net.i2p.util.Clock; import net.i2p.util.ConcurrentHashSet; @@ -843,4 +844,13 @@ public class I2PAppContext { public boolean isRouterContext() { return false; } + + /** + * Use this to connect to the router in the same JVM. + * @return always null in I2PAppContext, the client manager if in RouterContext + * @since 0.8.3 + */ + public InternalClientManager internalClientManager() { + return null; + } } diff --git a/core/java/src/net/i2p/client/ClientWriterRunner.java b/core/java/src/net/i2p/client/ClientWriterRunner.java index 056208fb5..f69148da3 100644 --- a/core/java/src/net/i2p/client/ClientWriterRunner.java +++ b/core/java/src/net/i2p/client/ClientWriterRunner.java @@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageImpl; import net.i2p.data.i2cp.I2CPMessageException; +import net.i2p.internal.PoisonI2CPMessage; import net.i2p.util.I2PAppThread; /** @@ -50,7 +51,7 @@ class ClientWriterRunner implements Runnable { public void stopWriting() { _messagesToWrite.clear(); try { - _messagesToWrite.put(new PoisonMessage()); + _messagesToWrite.put(new PoisonI2CPMessage()); } catch (InterruptedException ie) {} } @@ -62,7 +63,7 @@ class ClientWriterRunner implements Runnable { } catch (InterruptedException ie) { continue; } - if (msg.getType() == PoisonMessage.MESSAGE_TYPE) + if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE) break; // only thread, we don't need synchronized try { @@ -80,18 +81,4 @@ class ClientWriterRunner implements Runnable { } _messagesToWrite.clear(); } - - /** - * End-of-stream msg used to stop the concurrent queue - * See http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html - * - */ - private static class PoisonMessage extends I2CPMessageImpl { - public static final int MESSAGE_TYPE = 999999; - public int getType() { - return MESSAGE_TYPE; - } - public void doReadMessage(InputStream buf, int size) throws I2CPMessageException, IOException {} - public byte[] doWriteMessage() throws I2CPMessageException, IOException { return null; } - } } diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 72cc8b406..639ac5e9c 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -39,8 +39,10 @@ import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.SessionId; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.InternalClientManager; +import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PThread; -import net.i2p.util.InternalSocket; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -79,6 +81,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** where we pipe our messages */ protected /* FIXME final FIXME */OutputStream _out; + /** + * Used for internal connections to the router. + * If this is set, _socket, _writer, and _out will be null. + * @since 0.8.3 + */ + protected I2CPMessageQueue _queue; + /** who we send events to */ protected I2PSessionListener _sessionListener; @@ -285,17 +294,27 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa long startConnect = _context.clock().now(); try { - // If we are in the router JVM, connect using the interal pseudo-socket - _socket = InternalSocket.getSocket(_hostname, _portNum); - // _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it. - _out = _socket.getOutputStream(); - synchronized (_out) { - _out.write(I2PClient.PROTOCOL_BYTE); - _out.flush(); + // If we are in the router JVM, connect using the interal queue + if (_context.isRouterContext()) { + // _socket, _out, and _writer remain null + InternalClientManager mgr = _context.internalClientManager(); + if (mgr == null) + throw new I2PSessionException("Router is not ready for connections"); + // the following may throw an I2PSessionException + _queue = mgr.connect(); + _reader = new QueuedI2CPMessageReader(_queue, this); + } else { + _socket = new Socket(_hostname, _portNum); + // _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it. + _out = _socket.getOutputStream(); + synchronized (_out) { + _out.write(I2PClient.PROTOCOL_BYTE); + _out.flush(); + } + _writer = new ClientWriterRunner(_out, this); + InputStream in = _socket.getInputStream(); + _reader = new I2CPMessageReader(in, this); } - _writer = new ClientWriterRunner(_out, this); - InputStream in = _socket.getInputStream(); - _reader = new I2CPMessageReader(in, this); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading"); _reader.startReading(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate"); @@ -567,9 +586,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa * @throws I2PSessionException if the message is malformed or there is an error writing it out */ void sendMessage(I2CPMessage message) throws I2PSessionException { - if (isClosed() || _writer == null) + if (isClosed()) throw new I2PSessionException("Already closed"); - _writer.addMessage(message); + else if (_queue != null) + _queue.offer(message); // internal + else if (_writer == null) + throw new I2PSessionException("Already closed"); + else + _writer.addMessage(message); } /** @@ -581,8 +605,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa // Only log as WARN if the router went away int level; String msgpfx; - if ((error instanceof EOFException) || - (error.getMessage() != null && error.getMessage().startsWith("Pipe closed"))) { + if (error instanceof EOFException) { level = Log.WARN; msgpfx = "Router closed connection: "; } else { @@ -647,6 +670,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa _reader.stopReading(); _reader = null; } + if (_queue != null) { + // internal + _queue.close(); + } if (_writer != null) { _writer.stopWriting(); _writer = null; diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index f4bc3e812..c2da90217 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -19,6 +19,9 @@ import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.I2CPMessageReader; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.InternalClientManager; +import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PThread; import net.i2p.util.InternalSocket; @@ -72,16 +75,26 @@ class I2PSimpleSession extends I2PSessionImpl2 { notifier.start(); try { - // If we are in the router JVM, connect using the interal pseudo-socket - _socket = InternalSocket.getSocket(_hostname, _portNum); - _out = _socket.getOutputStream(); - synchronized (_out) { - _out.write(I2PClient.PROTOCOL_BYTE); - _out.flush(); + // If we are in the router JVM, connect using the interal queue + if (_context.isRouterContext()) { + // _socket, _out, and _writer remain null + InternalClientManager mgr = _context.internalClientManager(); + if (mgr == null) + throw new I2PSessionException("Router is not ready for connections"); + // the following may throw an I2PSessionException + _queue = mgr.connect(); + _reader = new QueuedI2CPMessageReader(_queue, this); + } else { + _socket = new Socket(_hostname, _portNum); + _out = _socket.getOutputStream(); + synchronized (_out) { + _out.write(I2PClient.PROTOCOL_BYTE); + _out.flush(); + } + _writer = new ClientWriterRunner(_out, this); + InputStream in = _socket.getInputStream(); + _reader = new I2CPMessageReader(in, this); } - _writer = new ClientWriterRunner(_out, this); - InputStream in = _socket.getInputStream(); - _reader = new I2CPMessageReader(in, this); _reader.startReading(); } catch (UnknownHostException uhe) { diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java index 461c4b08a..4ac902a48 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java @@ -27,11 +27,11 @@ import net.i2p.util.Log; public class I2CPMessageReader { private final static Log _log = new Log(I2CPMessageReader.class); private InputStream _stream; - private I2CPMessageEventListener _listener; - private I2CPMessageReaderRunner _reader; - private Thread _readerThread; + protected I2CPMessageEventListener _listener; + protected I2CPMessageReaderRunner _reader; + protected Thread _readerThread; - private static volatile long __readerId = 0; + protected static volatile long __readerId = 0; public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) { _stream = stream; @@ -42,6 +42,14 @@ public class I2CPMessageReader { _readerThread.setName("I2CP Reader " + (++__readerId)); } + /** + * For internal extension only. No stream. + * @since 0.8.3 + */ + protected I2CPMessageReader(I2CPMessageEventListener lsnr) { + setListener(lsnr); + } + public void setListener(I2CPMessageEventListener lsnr) { _listener = lsnr; } @@ -114,9 +122,9 @@ public class I2CPMessageReader { public void disconnected(I2CPMessageReader reader); } - private class I2CPMessageReaderRunner implements Runnable { - private volatile boolean _doRun; - private volatile boolean _stayAlive; + protected class I2CPMessageReaderRunner implements Runnable { + protected volatile boolean _doRun; + protected volatile boolean _stayAlive; public I2CPMessageReaderRunner() { _doRun = true; diff --git a/core/java/src/net/i2p/internal/I2CPMessageQueue.java b/core/java/src/net/i2p/internal/I2CPMessageQueue.java new file mode 100644 index 000000000..93bea3a3f --- /dev/null +++ b/core/java/src/net/i2p/internal/I2CPMessageQueue.java @@ -0,0 +1,51 @@ +package net.i2p.internal; + +import net.i2p.data.i2cp.I2CPMessage; + +/** + * Contains the methods to talk to a router or client via I2CP, + * when both are in the same JVM. + * This interface contains methods to access two queues, + * one for transmission and one for receiving. + * The methods are identical to those in java.util.concurrent.BlockingQueue. + * + * Reading may be done in a thread using the QueuedI2CPMessageReader class. + * Non-blocking writing may be done directly with offer(). + * + * @author zzz + * @since 0.8.3 + */ +public abstract class I2CPMessageQueue { + + /** + * Send a message, nonblocking. + * @return success (false if no space available) + */ + public abstract boolean offer(I2CPMessage msg); + + /** + * Receive a message, nonblocking. + * Unused for now. + * @return message or null if none available + */ + public abstract I2CPMessage poll(); + + /** + * Send a message, blocking until space is available. + * Unused for now. + */ + public abstract void put(I2CPMessage msg) throws InterruptedException; + + /** + * Receive a message, blocking until one is available. + * @return message + */ + public abstract I2CPMessage take() throws InterruptedException; + + /** + * == offer(new PoisonI2CPMessage()); + */ + public void close() { + offer(new PoisonI2CPMessage()); + } +} diff --git a/core/java/src/net/i2p/internal/InternalClientManager.java b/core/java/src/net/i2p/internal/InternalClientManager.java new file mode 100644 index 000000000..a923fb9f7 --- /dev/null +++ b/core/java/src/net/i2p/internal/InternalClientManager.java @@ -0,0 +1,19 @@ +package net.i2p.internal; + +import net.i2p.client.I2PSessionException; +import net.i2p.data.i2cp.I2CPMessage; + +/** + * A manager for the in-JVM I2CP message interface + * + * @author zzz + * @since 0.8.3 + */ +public interface InternalClientManager { + + /** + * Connect to the router, receiving a message queue to talk to the router with. + * @throws I2PSessionException if the router isn't ready + */ + public I2CPMessageQueue connect() throws I2PSessionException; +} diff --git a/core/java/src/net/i2p/internal/PoisonI2CPMessage.java b/core/java/src/net/i2p/internal/PoisonI2CPMessage.java new file mode 100644 index 000000000..23b99db9a --- /dev/null +++ b/core/java/src/net/i2p/internal/PoisonI2CPMessage.java @@ -0,0 +1,58 @@ +package net.i2p.internal; + +import java.io.InputStream; + +import net.i2p.data.i2cp.I2CPMessageException; +import net.i2p.data.i2cp.I2CPMessageImpl; + +/** + * For marking end-of-queues in a standard manner. + * Don't actually send it. + * + * @author zzz + * @since 0.8.3 + */ +public class PoisonI2CPMessage extends I2CPMessageImpl { + public final static int MESSAGE_TYPE = 999999; + + public PoisonI2CPMessage() { + super(); + } + + /** + * @deprecated don't do this + * @throws I2CPMessageException always + */ + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException { + throw new I2CPMessageException("Don't do this"); + } + + /** + * @deprecated don't do this + * @throws I2CPMessageException always + */ + protected byte[] doWriteMessage() throws I2CPMessageException { + throw new I2CPMessageException("Don't do this"); + } + + public int getType() { + return MESSAGE_TYPE; + } + + /* FIXME missing hashCode() method FIXME */ + @Override + public boolean equals(Object object) { + if ((object != null) && (object instanceof PoisonI2CPMessage)) { + return true; + } + + return false; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("[PoisonMessage]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java b/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java new file mode 100644 index 000000000..da128ceaa --- /dev/null +++ b/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java @@ -0,0 +1,55 @@ +package net.i2p.internal; + +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.data.i2cp.I2CPMessageReader; +import net.i2p.util.I2PThread; + +/** + * Get messages off an In-JVM queue, zero-copy + * + * @author zzz + * @since 0.8.3 + */ +public class QueuedI2CPMessageReader extends I2CPMessageReader { + private final I2CPMessageQueue in; + + public QueuedI2CPMessageReader(I2CPMessageQueue in, I2CPMessageEventListener lsnr) { + super(lsnr); + this.in = in; + _reader = new QueuedI2CPMessageReaderRunner(); + _readerThread = new I2PThread(_reader, "I2CP Internal Reader " + (++__readerId), true); + } + + protected class QueuedI2CPMessageReaderRunner extends I2CPMessageReaderRunner implements Runnable { + + public QueuedI2CPMessageReaderRunner() { + super(); + } + + @Override + public void run() { + while (_stayAlive) { + while (_doRun) { + // do read + I2CPMessage msg = null; + try { + msg = in.take(); + if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE) + cancelRunner(); + else + _listener.messageReceived(QueuedI2CPMessageReader.this, msg); + } catch (InterruptedException ie) {} + } + if (!_doRun) { + // pause .5 secs when we're paused + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + _listener.disconnected(QueuedI2CPMessageReader.this); + cancelRunner(); + } + } + } + } + } +} diff --git a/core/java/src/net/i2p/internal/package.html b/core/java/src/net/i2p/internal/package.html new file mode 100644 index 000000000..edac509f0 --- /dev/null +++ b/core/java/src/net/i2p/internal/package.html @@ -0,0 +1,7 @@ + +

+Interface and classes for a router and client +within the same JVM to directly pass I2CP messages using Queues +instead of serialized messages over socket streams. +

+ diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 3d5ed609e..cb3c63662 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -6,6 +6,7 @@ import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.Hash; +import net.i2p.internal.InternalClientManager; import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.peermanager.Calculator; @@ -34,7 +35,7 @@ import net.i2p.util.KeyRing; */ public class RouterContext extends I2PAppContext { private Router _router; - private ClientManagerFacade _clientManagerFacade; + private ClientManagerFacadeImpl _clientManagerFacade; private ClientMessagePool _clientMessagePool; private JobQueue _jobQueue; private InNetMessagePool _inNetMessagePool; @@ -106,10 +107,12 @@ public class RouterContext extends I2PAppContext { } public void initAll() { - if ("false".equals(getProperty("i2p.dummyClientFacade", "false"))) - _clientManagerFacade = new ClientManagerFacadeImpl(this); - else - _clientManagerFacade = new DummyClientManagerFacade(this); + if (getBooleanProperty("i2p.dummyClientFacade")) + System.err.println("i2p.dummpClientFacade currently unsupported"); + _clientManagerFacade = new ClientManagerFacadeImpl(this); + // removed since it doesn't implement InternalClientManager for now + //else + // _clientManagerFacade = new DummyClientManagerFacade(this); _clientMessagePool = new ClientMessagePool(this); _jobQueue = new JobQueue(this); _inNetMessagePool = new InNetMessagePool(this); @@ -395,4 +398,13 @@ public class RouterContext extends I2PAppContext { public boolean isRouterContext() { return true; } + + /** + * Use this to connect to the router in the same JVM. + * @return the client manager + * @since 0.8.3 + */ + public InternalClientManager internalClientManager() { + return _clientManagerFacade; + } } diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index b3468e4e0..5807aa098 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -52,7 +52,7 @@ import net.i2p.util.SimpleTimer; */ public class ClientConnectionRunner { private Log _log; - private RouterContext _context; + protected final RouterContext _context; private ClientManager _manager; /** socket for this particular peer connection */ private Socket _socket; @@ -71,7 +71,7 @@ public class ClientConnectionRunner { /** set of messageIds created but not yet ACCEPTED */ private Set _acceptedPending; /** thingy that does stuff */ - private I2CPMessageReader _reader; + protected I2CPMessageReader _reader; /** just for this destination */ private SessionKeyManager _sessionKeyManager; /** @@ -469,18 +469,8 @@ public class ClientConnectionRunner { _log.warn("Error sending I2CP message - client went away", eofe); stopRunning(); } catch (IOException ioe) { - // only warn if client went away - int level; - String emsg; - if (ioe.getMessage() != null && ioe.getMessage().startsWith("Pipe closed")) { - level = Log.WARN; - emsg = "Error sending I2CP message - client went away"; - } else { - level = Log.ERROR; - emsg = "IO Error sending I2CP message to client"; - } - if (_log.shouldLog(level)) - _log.log(level, emsg, ioe); + if (_log.shouldLog(Log.ERROR)) + _log.error("IO Error sending I2CP message to client", ioe); stopRunning(); } catch (Throwable t) { _log.log(Log.CRIT, "Unhandled exception sending I2CP message to client", t); diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index 7d866ab0b..948f895bb 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -15,7 +15,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import net.i2p.client.I2PSessionException; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.DataHelper; import net.i2p.data.Destination; @@ -23,8 +25,10 @@ import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.Payload; import net.i2p.data.TunnelId; +import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.SessionConfig; +import net.i2p.internal.I2CPMessageQueue; import net.i2p.router.ClientManagerFacade; import net.i2p.router.ClientMessage; import net.i2p.router.Job; @@ -42,7 +46,6 @@ import net.i2p.util.Log; public class ClientManager { private Log _log; private ClientListenerRunner _listener; - private ClientListenerRunner _internalListener; private final HashMap _runners; // Destination --> ClientConnectionRunner private final Set _pendingRunners; // ClientConnectionRunner for clients w/out a Dest yet private RouterContext _ctx; @@ -69,11 +72,6 @@ public class ClientManager { t.setName("ClientListener:" + port); t.setDaemon(true); t.start(); - _internalListener = new InternalClientListenerRunner(_ctx, this, port); - t = new I2PThread(_internalListener); - t.setName("ClientListener:" + port + "-i"); - t.setDaemon(true); - t.start(); } public void restart() { @@ -97,7 +95,6 @@ public class ClientManager { public void shutdown() { _log.info("Shutting down the ClientManager"); _listener.stopListening(); - _internalListener.stopListening(); Set runners = new HashSet(); synchronized (_runners) { for (Iterator iter = _runners.values().iterator(); iter.hasNext();) { @@ -117,6 +114,23 @@ public class ClientManager { } } + /** + * The InternalClientManager interface. + * Connects to the router, receiving a message queue to talk to the router with. + * Might throw I2PSessionException if the router isn't ready, someday. + * @since 0.8.3 + */ + public I2CPMessageQueue internalConnect() { + // for now we make these unlimited size + LinkedBlockingQueue in = new LinkedBlockingQueue(); + LinkedBlockingQueue out = new LinkedBlockingQueue(); + I2CPMessageQueue myQueue = new I2CPMessageQueueImpl(in, out); + I2CPMessageQueue hisQueue = new I2CPMessageQueueImpl(out, in); + ClientConnectionRunner runner = new QueuedClientConnectionRunner(_ctx, this, myQueue); + registerConnection(runner); + return hisQueue; + } + public boolean isAlive() { return _listener.isListening(); } public void registerConnection(ClientConnectionRunner runner) { diff --git a/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java index 066d6cc35..5fd0bbc28 100644 --- a/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java +++ b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.Set; +import net.i2p.client.I2PSessionException; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.DataHelper; import net.i2p.data.Destination; @@ -21,6 +22,8 @@ import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.SessionConfig; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.InternalClientManager; import net.i2p.router.ClientManagerFacade; import net.i2p.router.ClientMessage; import net.i2p.router.Job; @@ -32,7 +35,7 @@ import net.i2p.util.Log; * * @author jrandom */ -public class ClientManagerFacadeImpl extends ClientManagerFacade { +public class ClientManagerFacadeImpl extends ClientManagerFacade implements InternalClientManager { private final static Log _log = new Log(ClientManagerFacadeImpl.class); private ClientManager _manager; private RouterContext _context; @@ -220,4 +223,16 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade { else return Collections.EMPTY_SET; } + + /** + * The InternalClientManager interface. + * Connect to the router, receiving a message queue to talk to the router with. + * @throws I2PSessionException if the router isn't ready + * @since 0.8.3 + */ + public I2CPMessageQueue connect() throws I2PSessionException { + if (_manager != null) + return _manager.internalConnect(); + throw new I2PSessionException("No manager yet"); + } } diff --git a/router/java/src/net/i2p/router/client/ClientWriterRunner.java b/router/java/src/net/i2p/router/client/ClientWriterRunner.java index 49fcddcc2..b93a4e5f4 100644 --- a/router/java/src/net/i2p/router/client/ClientWriterRunner.java +++ b/router/java/src/net/i2p/router/client/ClientWriterRunner.java @@ -8,6 +8,7 @@ import java.util.concurrent.LinkedBlockingQueue; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageImpl; import net.i2p.data.i2cp.I2CPMessageException; +import net.i2p.internal.PoisonI2CPMessage; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -52,7 +53,7 @@ class ClientWriterRunner implements Runnable { public void stopWriting() { _messagesToWrite.clear(); try { - _messagesToWrite.put(new PoisonMessage()); + _messagesToWrite.put(new PoisonI2CPMessage()); } catch (InterruptedException ie) {} } @@ -64,23 +65,9 @@ class ClientWriterRunner implements Runnable { } catch (InterruptedException ie) { continue; } - if (msg.getType() == PoisonMessage.MESSAGE_TYPE) + if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE) break; _runner.writeMessage(msg); } } - - /** - * End-of-stream msg used to stop the concurrent queue - * See http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html - * - */ - private static class PoisonMessage extends I2CPMessageImpl { - public static final int MESSAGE_TYPE = 999999; - public int getType() { - return MESSAGE_TYPE; - } - public void doReadMessage(InputStream buf, int size) throws I2CPMessageException, IOException {} - public byte[] doWriteMessage() throws I2CPMessageException, IOException { return null; } - } } diff --git a/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java b/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java new file mode 100644 index 000000000..f65b06176 --- /dev/null +++ b/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java @@ -0,0 +1,57 @@ +package net.i2p.router.client; + +import java.util.concurrent.BlockingQueue; + +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.internal.I2CPMessageQueue; + +/** + * Contains the methods to talk to a router or client via I2CP, + * when both are in the same JVM. + * This interface contains methods to access two queues, + * one for transmission and one for receiving. + * The methods are identical to those in java.util.concurrent.BlockingQueue + * + * @author zzz + * @since 0.8.3 + */ +class I2CPMessageQueueImpl extends I2CPMessageQueue { + private final BlockingQueue _in; + private final BlockingQueue _out; + + public I2CPMessageQueueImpl(BlockingQueue in, BlockingQueue out) { + _in = in; + _out = out; + } + + /** + * Send a message, nonblocking + * @return success (false if no space available) + */ + public boolean offer(I2CPMessage msg) { + return _out.offer(msg); + } + + /** + * Receive a message, nonblocking + * @return message or null if none available + */ + public I2CPMessage poll() { + return _in.poll(); + } + + /** + * Send a message, blocking until space is available + */ + public void put(I2CPMessage msg) throws InterruptedException { + _out.put(msg); + } + + /** + * Receive a message, blocking until one is available + * @return message + */ + public I2CPMessage take() throws InterruptedException { + return _in.take(); + } +} diff --git a/router/java/src/net/i2p/router/client/InternalClientListenerRunner.java b/router/java/src/net/i2p/router/client/InternalClientListenerRunner.java deleted file mode 100644 index 995c69400..000000000 --- a/router/java/src/net/i2p/router/client/InternalClientListenerRunner.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.i2p.router.client; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.io.IOException; -import java.net.Socket; - -import net.i2p.router.RouterContext; -import net.i2p.util.Log; -import net.i2p.util.InternalServerSocket; - -/** - * Listen for in-JVM connections on the internal "socket" - * - * @author zzz - * @since 0.7.9 - */ -public class InternalClientListenerRunner extends ClientListenerRunner { - - public InternalClientListenerRunner(RouterContext context, ClientManager manager, int port) { - super(context, manager, port); - _log = _context.logManager().getLog(InternalClientListenerRunner.class); - } - - /** - * Start up the socket listener, listens for connections, and - * fires those connections off via {@link #runConnection runConnection}. - * This only returns if the socket cannot be opened or there is a catastrophic - * failure. - * - */ - public void runServer() { - try { - if (_log.shouldLog(Log.INFO)) - _log.info("Listening on internal port " + _port); - _socket = new InternalServerSocket(_port); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("InternalServerSocket created, before accept: " + _socket); - - _listening = true; - _running = true; - while (_running) { - try { - Socket socket = _socket.accept(); - if (validate(socket)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Internal connection received"); - runConnection(socket); - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn("Refused connection from " + socket.getInetAddress()); - try { - socket.close(); - } catch (IOException ioe) {} - } - } catch (IOException ioe) { - if (_context.router().isAlive()) - _log.error("Server error accepting", ioe); - } catch (Throwable t) { - if (_context.router().isAlive()) - _log.error("Fatal error running client listener - killing the thread!", t); - _listening = false; - return; - } - } - } catch (IOException ioe) { - if (_context.router().isAlive()) - _log.error("Error listening on internal port " + _port, ioe); - } - - _listening = false; - if (_socket != null) { - try { _socket.close(); } catch (IOException ioe) {} - _socket = null; - } - - - if (_context.router().isAlive()) - _log.error("CANCELING I2CP LISTEN", new Exception("I2CP Listen cancelled!!!")); - _running = false; - } -} diff --git a/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java new file mode 100644 index 000000000..ea2cc3d30 --- /dev/null +++ b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java @@ -0,0 +1,76 @@ +package net.i2p.router.client; + +import java.io.IOException; + +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.data.i2cp.I2CPMessageException; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.QueuedI2CPMessageReader; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * Zero-copy in-JVM. + * While super() starts both a reader and a writer thread, we only need a reader thread here. + * + * @author zzz + * @since 0.8.3 + */ +public class QueuedClientConnectionRunner extends ClientConnectionRunner { + private final I2CPMessageQueue queue; + + /** + * Create a new runner with the given queues + * + */ + public QueuedClientConnectionRunner(RouterContext context, ClientManager manager, I2CPMessageQueue queue) { + super(context, manager, null); + this.queue = queue; + } + + + + /** + * Starts the reader thread. Does not call super(). + */ + @Override + public void startRunning() { + _reader = new QueuedI2CPMessageReader(this.queue, new ClientMessageEventListener(_context, this)); + _reader.startReading(); + } + + /** + * Calls super() to stop the reader, and sends a poison message to the client. + */ + @Override + void stopRunning() { + super.stopRunning(); + queue.close(); + } + + /** + * In super(), doSend queues it to the writer thread and + * the writer thread calls writeMessage() to write to the output stream. + * Since we have no writer thread this shouldn't happen. + */ + @Override + void writeMessage(I2CPMessage msg) { + throw new RuntimeException("huh?"); + } + + /** + * Actually send the I2CPMessage to the client. + * Nonblocking. + */ + @Override + void doSend(I2CPMessage msg) throws I2CPMessageException { + // This will never fail, for now, as the router uses unbounded queues + // Perhaps in the future we may want to use bounded queues, + // with non-blocking writes for the router + // and blocking writes for the client? + boolean success = queue.offer(msg); + if (!success) + throw new I2CPMessageException("I2CP write to queue failed"); + } + +} From ae6a6a1d9c304f597bb255af43a702db5808ae9b Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Dec 2010 15:20:00 +0000 Subject: [PATCH 02/67] * I2PTunnel: Get Log from the logManager instead of instantiating, so we may adjust the levels on the fly --- .../net/i2p/i2ptunnel/I2PTunnelClient.java | 2 - .../i2p/i2ptunnel/I2PTunnelClientBase.java | 14 +++++-- .../i2p/i2ptunnel/I2PTunnelConnectClient.java | 5 +-- .../i2ptunnel/I2PTunnelHTTPBidirServer.java | 1 - .../i2p/i2ptunnel/I2PTunnelHTTPClient.java | 5 +-- .../i2ptunnel/I2PTunnelHTTPClientBase.java | 2 +- .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 42 ++++++++++++------- .../net/i2p/i2ptunnel/I2PTunnelIRCClient.java | 16 +++---- .../net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 11 ++--- .../net/i2p/i2ptunnel/I2PTunnelServer.java | 6 ++- .../i2ptunnel/socks/I2PSOCKSIRCTunnel.java | 1 - .../i2p/i2ptunnel/socks/I2PSOCKSTunnel.java | 1 - .../udpTunnel/I2PTunnelUDPClientBase.java | 1 - .../udpTunnel/I2PTunnelUDPServerBase.java | 3 +- 14 files changed, 60 insertions(+), 50 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index 053fc61ce..bfde2fb32 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java @@ -16,8 +16,6 @@ import net.i2p.util.Log; public class I2PTunnelClient extends I2PTunnelClientBase { - private static final Log _log = new Log(I2PTunnelClient.class); - /** list of Destination objects that we point at */ protected List dests; private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1 diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index 54d31d29e..2dcb2af15 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -34,9 +34,9 @@ import net.i2p.util.SimpleTimer; public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable { - private static final Log _log = new Log(I2PTunnelClientBase.class); - protected I2PAppContext _context; - protected Logging l; + protected final Log _log; + protected final I2PAppContext _context; + protected final Logging l; static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000; @@ -109,6 +109,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna _context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); + _log = _context.logManager().getLog(getClass()); Thread t = new I2PAppThread(this); t.setName("Client " + _clientId); @@ -163,6 +164,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna _context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); + _log = _context.logManager().getLog(getClass()); // normalize path so we can find it if (pkf != null) { @@ -321,6 +323,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna * badly that we cant create a socketManager */ protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) { + // shadows instance _log + Log _log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class); if (socketManager != null) { I2PSession s = socketManager.getSession(); if ( (s == null) || (s.isClosed()) ) { @@ -378,6 +382,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna * badly that we cant create a socketManager */ protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf, Logging log) { + // shadows instance _log + Log _log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class); Properties props = new Properties(); props.putAll(tunnel.getClientOptions()); int portNum = 7654; @@ -696,7 +702,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna try { s.close(); } catch (IOException ex) { - _log.error("Could not close socket", ex); + //_log.error("Could not close socket", ex); } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index e151d733d..70265a154 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -58,7 +58,6 @@ import net.i2p.util.Log; * @author zzz a stripped-down I2PTunnelHTTPClient */ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable { - private static final Log _log = new Log(I2PTunnelConnectClient.class); private final static byte[] ERR_DESTINATION_UNKNOWN = ("HTTP/1.1 503 Service Unavailable\r\n"+ @@ -340,8 +339,8 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R _requestId = id; } public void run() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Timeout occured requesting " + _target); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Timeout occured requesting " + _target); handleConnectClientException(new RuntimeException("Timeout"), _out, _target, _usingProxy, _wwwProxy, _requestId); closeSocket(_socket); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPBidirServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPBidirServer.java index 54b2046cf..ff1f37661 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPBidirServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPBidirServer.java @@ -11,7 +11,6 @@ import net.i2p.util.EventDispatcher; import net.i2p.util.Log; public class I2PTunnelHTTPBidirServer extends I2PTunnelHTTPServer { - private final static Log log = new Log(I2PTunnelHTTPBidirServer.class); public I2PTunnelHTTPBidirServer(InetAddress host, int port, int proxyport, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super(host, port, privData, spoofHost, l, notifyThis, tunnel); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 8288e702f..6c5c38e5e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -61,7 +61,6 @@ import net.i2p.util.Translate; * */ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runnable { - private static final Log _log = new Log(I2PTunnelHTTPClient.class); private HashMap addressHelpers = new HashMap(); @@ -894,8 +893,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn _requestId = id; } public void run() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Timeout occured requesting " + _target); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Timeout occured requesting " + _target); handleHTTPClientException(new RuntimeException("Timeout"), _out, _target, _usingProxy, _wwwProxy, _requestId); closeSocket(_socket); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java index 131a02dbc..0f1b35c32 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -25,7 +25,7 @@ import net.i2p.util.Log; * @since 0.8.2 */ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable { - private static final Log _log = new Log(I2PTunnelHTTPClientBase.class); + protected final List _proxyList; protected final static byte[] ERR_NO_OUTPROXY = diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index ca568ab92..027931d9b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -30,7 +30,7 @@ import net.i2p.data.Base32; * */ public class I2PTunnelHTTPServer extends I2PTunnelServer { - private final static Log _log = new Log(I2PTunnelHTTPServer.class); + /** what Host: should we seem to be to the webserver? */ private String _spoofHost; private static final String HASH_HEADER = "X-I2P-DestHash"; @@ -118,7 +118,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { useGZIP = true; if (allowGZIP && useGZIP) { - I2PAppThread req = new I2PAppThread(new CompressedRequestor(s, socket, modifiedHeader), Thread.currentThread().getName()+".hc"); + I2PAppThread req = new I2PAppThread(new CompressedRequestor(s, socket, modifiedHeader, _log), Thread.currentThread().getName()+".hc"); req.start(); } else { new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null); @@ -152,14 +152,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } private static class CompressedRequestor implements Runnable { - private Socket _webserver; - private I2PSocket _browser; - private String _headers; - public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) { + private final Socket _webserver; + private final I2PSocket _browser; + private final String _headers; + // shadows _log in super() + private final Log _log; + + public CompressedRequestor(Socket webserver, I2PSocket browser, String headers, Log log) { _webserver = webserver; _browser = browser; _headers = headers; + _log = log; } + public void run() { if (_log.shouldLog(Log.INFO)) _log.info("Compressed requestor running"); @@ -174,7 +179,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { _log.info("request headers: " + _headers); serverout.write(_headers.getBytes()); browserin = _browser.getInputStream(); - I2PAppThread sender = new I2PAppThread(new Sender(serverout, browserin, "server: browser to server"), Thread.currentThread().getName() + "hcs"); + I2PAppThread sender = new I2PAppThread(new Sender(serverout, browserin, "server: browser to server", _log), Thread.currentThread().getName() + "hcs"); sender.start(); browserout = _browser.getOutputStream(); @@ -197,7 +202,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { throw new IOException("getInputStream NPE"); } CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout); - Sender s = new Sender(compressedOut, serverin, "server: server to browser"); + Sender s = new Sender(compressedOut, serverin, "server: server to browser", _log); if (_log.shouldLog(Log.INFO)) _log.info("Before pumping the compressed response"); s.run(); // same thread @@ -216,14 +221,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } private static class Sender implements Runnable { - private OutputStream _out; - private InputStream _in; - private String _name; - public Sender(OutputStream out, InputStream in, String name) { + private final OutputStream _out; + private final InputStream _in; + private final String _name; + // shadows _log in super() + private final Log _log; + + public Sender(OutputStream out, InputStream in, String name, Log log) { _out = out; _in = in; _name = name; + _log = log; } + public void run() { if (_log.shouldLog(Log.INFO)) _log.info(_name + ": Begin sending"); @@ -260,16 +270,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { protected boolean shouldCompress() { return true; } @Override protected void finishHeaders() throws IOException { - if (_log.shouldLog(Log.INFO)) - _log.info("Including x-i2p-gzip as the content encoding in the response"); + //if (_log.shouldLog(Log.INFO)) + // _log.info("Including x-i2p-gzip as the content encoding in the response"); out.write("Content-encoding: x-i2p-gzip\r\n".getBytes()); super.finishHeaders(); } @Override protected void beginProcessing() throws IOException { - if (_log.shouldLog(Log.INFO)) - _log.info("Beginning compression processing"); + //if (_log.shouldLog(Log.INFO)) + // _log.info("Beginning compression processing"); //out.flush(); _gzipOut = new InternalGZIPOutputStream(out); out = _gzipOut; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java index 850a1fedc..d5b3dda65 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java @@ -20,8 +20,6 @@ import net.i2p.util.Log; */ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable { - private static final Log _log = new Log(I2PTunnelIRCClient.class); - /** used to assign unique IDs to the threads / clients. no logic or functionality */ private static volatile long __clientId = 0; @@ -130,6 +128,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable private Socket local; private I2PSocket remote; private StringBuffer expectedPong; + // shadows _log in super() + private final Log _log = new Log(I2PTunnelIRCClient.class); public IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) { local=_local; @@ -207,6 +207,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable private Socket local; private I2PSocket remote; private StringBuffer expectedPong; + // shadows _log in super() + private final Log _log = new Log(I2PTunnelIRCClient.class); public IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) { local=_local; @@ -308,7 +310,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable try { command = field[idx++]; } catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command? { - _log.warn("Dropping defective message: index out of bounds while extracting command."); + //_log.warn("Dropping defective message: index out of bounds while extracting command."); return null; } @@ -431,13 +433,13 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable rv = "PING " + field[1]; expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")"); + //if (_log.shouldLog(Log.ERROR)) + // _log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")"); rv = null; } - if (_log.shouldLog(Log.WARN)) - _log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]"); + //if (_log.shouldLog(Log.WARN)) + // _log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]"); return rv; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 1d05103e7..f1b6864bd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -62,9 +62,6 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { public static final String PROP_HOSTNAME="ircserver.fakeHostname"; public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p"; - private static final Log _log = new Log(I2PTunnelIRCServer.class); - - /** * @throws IllegalArgumentException if the I2PTunnel does not contain * valid config to contact the router @@ -181,8 +178,8 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { if (++lineCount > 10) throw new IOException("Too many lines before USER or SERVER, giving up"); s = s.trim(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Got line: " + s); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Got line: " + s); String field[]=s.split(" ",5); String command; @@ -214,8 +211,8 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { if ("SERVER".equalsIgnoreCase(command)) break; } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("All done, sending: " + buf.toString()); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("All done, sending: " + buf.toString()); return buf.toString(); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index fc39e1996..76287f9cc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -30,8 +30,7 @@ import net.i2p.util.Log; public class I2PTunnelServer extends I2PTunnelTask implements Runnable { - private final static Log _log = new Log(I2PTunnelServer.class); - + protected final Log _log; protected I2PSocketManager sockMgr; protected I2PServerSocket i2pss; @@ -67,6 +66,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { */ public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); + _log = tunnel.getContext().logManager().getLog(getClass()); ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData)); SetUsePool(tunnel); init(host, port, bais, privData, l); @@ -79,6 +79,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); + _log = tunnel.getContext().logManager().getLog(getClass()); SetUsePool(tunnel); FileInputStream fis = null; try { @@ -99,6 +100,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { */ public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); + _log = tunnel.getContext().logManager().getLog(getClass()); SetUsePool(tunnel); init(host, port, privData, privkeyname, l); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java index 01888d8d1..84e66cf72 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java @@ -30,7 +30,6 @@ import net.i2p.util.Log; */ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel { - private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(I2PSOCKSIRCTunnel.class); private static int __clientId = 0; /** @param pkf private key file name or null for transient key */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java index 14cafbdfd..10d51fe2e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -26,7 +26,6 @@ import net.i2p.util.Log; public class I2PSOCKSTunnel extends I2PTunnelClientBase { - private static final Log _log = new Log(I2PSOCKSTunnel.class); private HashMap> proxies = null; // port# + "" or "default" -> hostname list protected Destination outProxyDest = null; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java index 38c82a70c..d9c5fcd03 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -45,7 +45,6 @@ import net.i2p.util.Log; */ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink { - private static final Log _log = new Log(I2PTunnelUDPClientBase.class); protected I2PAppContext _context; protected Logging l; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java index 6ba8379f9..4d43d5308 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java @@ -46,7 +46,7 @@ import net.i2p.util.Log; public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink { - private final static Log _log = new Log(I2PTunnelUDPServerBase.class); + private final Log _log; private final Object lock = new Object(); protected Object slock = new Object(); @@ -73,6 +73,7 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("UDPServer <- " + privkeyname, notifyThis, tunnel); + _log = tunnel.getContext().logManager().getLog(I2PTunnelUDPServerBase.class); FileInputStream fis = null; try { fis = new FileInputStream(privkey); From 5c73a6055183c29094d73fffa2b5a33ed158325e Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Dec 2010 16:51:04 +0000 Subject: [PATCH 03/67] * I2PTunnel: - Better logging of server errors - Return 503 to client before closing i2p socket if server is not there --- .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 28 +++++++++++++++---- .../net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 8 +++--- .../net/i2p/i2ptunnel/I2PTunnelServer.java | 24 ++++++++-------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 027931d9b..e2d81be40 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -37,6 +37,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { private static final String DEST64_HEADER = "X-I2P-DestB64"; private static final String DEST32_HEADER = "X-I2P-DestB32"; + private final static byte[] ERR_UNAVAILABLE = + ("HTTP/1.1 503 Service Unavailable\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "503 Service Unavailable<title></head>\n"+ + "<body><h2>503 Service Unavailable</h2>\n" + + "<p>This I2P eepsite is unavailable. It may be down or undergoing maintenance.</p>\n" + + "</body></html>") + .getBytes(); + public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super(host, port, privData, l, notifyThis, tunnel); setupI2PTunnelHTTPServer(spoofHost); @@ -124,12 +137,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null); } } catch (SocketException ex) { + try { + // Send a 503, so the user doesn't get an HTTP Proxy error message + // and blame his router or the network. + socket.getOutputStream().write(ERR_UNAVAILABLE); + } catch (IOException ioe) {} try { socket.close(); - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error while closing the received i2p con", ex); - } + } catch (IOException ioe) {} + if (_log.shouldLog(Log.ERROR)) + _log.error("Error connecting to HTTP server " + remoteHost + ':' + remotePort, ex); } catch (IOException ex) { try { socket.close(); @@ -148,7 +165,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { long timeToHandle = afterHandle - afterAccept; getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0); if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) - _log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); + _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + + " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } private static class CompressedRequestor implements Runnable { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index f1b6864bd..ed1367e11 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -123,12 +123,12 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { Socket s = new Socket(remoteHost, remotePort); new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null); } catch (SocketException ex) { + // TODO send the equivalent of a 503? try { socket.close(); - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error while closing the received i2p con", ex); - } + } catch (IOException ioe) {} + if (_log.shouldLog(Log.ERROR)) + _log.error("Error connecting to IRC server " + remoteHost + ':' + remotePort, ex); } catch (IOException ex) { try { socket.close(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 76287f9cc..6fc05daa4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -68,7 +68,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData)); - SetUsePool(tunnel); + setUsePool(tunnel); init(host, port, bais, privData, l); } @@ -80,7 +80,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); - SetUsePool(tunnel); + setUsePool(tunnel); FileInputStream fis = null; try { fis = new FileInputStream(privkey); @@ -101,13 +101,13 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); - SetUsePool(tunnel); + setUsePool(tunnel); init(host, port, privData, privkeyname, l); } - private void SetUsePool(I2PTunnel Tunnel) { - String usePool = Tunnel.getClientOptions().getProperty("i2ptunnel.usePool"); + private void setUsePool(I2PTunnel tunnel) { + String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool"); if (usePool != null) _usePool = "true".equalsIgnoreCase(usePool); else @@ -269,15 +269,14 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } public void run() { + I2PServerSocket i2pS_S = sockMgr.getServerSocket(); if (shouldUsePool()) { - I2PServerSocket i2pS_S = sockMgr.getServerSocket(); int handlers = getHandlerCount(); for (int i = 0; i < handlers; i++) { I2PAppThread handler = new I2PAppThread(new Handler(i2pS_S), "Handle Server " + i); handler.start(); } } else { - I2PServerSocket i2pS_S = sockMgr.getServerSocket(); while (true) { try { final I2PSocket i2ps = i2pS_S.accept(); @@ -340,17 +339,18 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } catch (SocketException ex) { try { socket.close(); - } catch (IOException ioe) { - _log.error("Error while closing the received i2p con", ex); - } + } catch (IOException ioe) {} + if (_log.shouldLog(Log.ERROR)) + _log.error("Error connecting to server " + remoteHost + ':' + remotePort, ex); } catch (IOException ex) { _log.error("Error while waiting for I2PConnections", ex); } long afterHandle = I2PAppContext.getGlobalContext().clock().now(); long timeToHandle = afterHandle - afterAccept; - if (timeToHandle > 1000) - _log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); + if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) + _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + + " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } } From e9e1890b148443de9e79b49c7f013498e5790610 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 3 Dec 2010 17:01:51 +0000 Subject: [PATCH 04/67] * I2PTunnel: - Extend header timeouts for HTTP and IRC server tunnels --- .../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java | 6 ++++-- .../java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index e2d81be40..1aa6a8347 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -36,6 +36,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { private static final String HASH_HEADER = "X-I2P-DestHash"; private static final String DEST64_HEADER = "X-I2P-DestB64"; private static final String DEST32_HEADER = "X-I2P-DestB32"; + private static final long HEADER_TIMEOUT = 60*1000; private final static byte[] ERR_UNAVAILABLE = ("HTTP/1.1 503 Service Unavailable\r\n"+ @@ -82,8 +83,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { //local is fast, so synchronously. Does not need that many //threads. try { - // give them 5 seconds to send in the HTTP request - socket.setReadTimeout(5*1000); + // The headers _should_ be in the first packet, but + // may not be, depending on the client-side options + socket.setReadTimeout(HEADER_TIMEOUT); InputStream in = socket.getInputStream(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index ed1367e11..4537389bd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -61,6 +61,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1"; public static final String PROP_HOSTNAME="ircserver.fakeHostname"; public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p"; + private static final long HEADER_TIMEOUT = 60*1000; /** * @throws IllegalArgumentException if the I2PTunnel does not contain @@ -105,8 +106,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { try { String modifiedRegistration; if(!this.method.equals("webirc")) { - // give them 15 seconds to send in the request - socket.setReadTimeout(15*1000); + // The headers _should_ be in the first packet, but + // may not be, depending on the client-side options + socket.setReadTimeout(HEADER_TIMEOUT); InputStream in = socket.getInputStream(); modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination())); socket.setReadTimeout(readTimeout); From fabbda659b2f12cdce64717e87286b5c8e47b4ae Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 3 Dec 2010 17:23:36 +0000 Subject: [PATCH 05/67] I2CP internal mode tweaks --- build.xml | 2 +- .../src/net/i2p/client/I2PSessionImpl.java | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/build.xml b/build.xml index f0c1c4973..8a6bf33d2 100644 --- a/build.xml +++ b/build.xml @@ -237,7 +237,7 @@ splitindex="true" doctitle="I2P Javadocs for Release ${release.number} Build ${build.number}" windowtitle="I2P Anonymous Network - Java Documentation - Version ${release.number}"> - <group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:freenet.support.CPUInformation:org.bouncycastle.crypto:org.bouncycastle.crypto.*:gnu.crypto.*:gnu.gettext:org.xlattice.crypto.filters:com.nettgryppa.security" /> + <group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.crypto:org.bouncycastle.crypto.*:gnu.crypto.*:gnu.gettext:org.xlattice.crypto.filters:com.nettgryppa.security" /> <group title="Streaming Library" packages="net.i2p.client.streaming" /> <group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:org.cybergarage.*:org.freenetproject" /> <group title="Router Console" packages="net.i2p.router.web" /> diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 639ac5e9c..4db4e1759 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -68,9 +68,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** currently granted lease set, or null */ private LeaseSet _leaseSet; - /** hostname of router */ + /** hostname of router - will be null if in RouterContext */ protected String _hostname; - /** port num to router */ + /** port num to router - will be 0 if in RouterContext */ protected int _portNum; /** socket for comm */ protected Socket _socket; @@ -181,15 +181,17 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa protected void loadConfig(Properties options) { _options = new Properties(); _options.putAll(filter(options)); - _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); - String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + ""); - try { - _portNum = Integer.parseInt(portNum); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getPrefix() + "Invalid port number specified, defaulting to " - + LISTEN_PORT, nfe); - _portNum = LISTEN_PORT; + if (!_context.isRouterContext()) { + _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); + String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + ""); + try { + _portNum = Integer.parseInt(portNum); + } catch (NumberFormatException nfe) { + if (_log.shouldLog(Log.WARN)) + _log.warn(getPrefix() + "Invalid port number specified, defaulting to " + + LISTEN_PORT, nfe); + _portNum = LISTEN_PORT; + } } // auto-add auth if required, not set in the options, and we are in the same JVM From 8976746867030620306222c46c547972ba1ca685 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 3 Dec 2010 20:40:34 +0000 Subject: [PATCH 06/67] mindless history.txt servlet --- .../src/net/i2p/router/web/LocaleWebAppHandler.java | 3 ++- apps/routerconsole/jsp/viewhistory.jsp | 12 ++++++++++++ apps/routerconsole/jsp/web.xml | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/routerconsole/jsp/viewhistory.jsp diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java index 91ede9e52..ebf5bcf9c 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java @@ -53,7 +53,8 @@ public class LocaleWebAppHandler extends WebApplicationHandler // home page pathInContext = "/index.jsp"; } else if (pathInContext.indexOf("/", 1) < 0 && - !pathInContext.endsWith(".jsp")) { + (!pathInContext.endsWith(".jsp")) && + (!pathInContext.endsWith(".txt"))) { // add .jsp to pages at top level pathInContext += ".jsp"; } diff --git a/apps/routerconsole/jsp/viewhistory.jsp b/apps/routerconsole/jsp/viewhistory.jsp new file mode 100644 index 000000000..6268abd5a --- /dev/null +++ b/apps/routerconsole/jsp/viewhistory.jsp @@ -0,0 +1,12 @@ +<% +/* + * USE CAUTION WHEN EDITING + * Trailing whitespace OR NEWLINE on the last line will cause + * IllegalStateExceptions !!! + * + * Do not tag this file for translation. + */ +response.setContentType("text/plain"); +String base = net.i2p.I2PAppContext.getGlobalContext().getBaseDir().getAbsolutePath(); +net.i2p.util.FileUtil.readFile("history.txt", base, response.getOutputStream()); +%> \ No newline at end of file diff --git a/apps/routerconsole/jsp/web.xml b/apps/routerconsole/jsp/web.xml index 8679abcb7..e5bdbeb32 100644 --- a/apps/routerconsole/jsp/web.xml +++ b/apps/routerconsole/jsp/web.xml @@ -17,6 +17,11 @@ <url-pattern>/javadoc/*</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>net.i2p.router.web.jsp.viewhistory_jsp</servlet-name> + <url-pattern>/history.txt</url-pattern> + </servlet-mapping> + <session-config> <session-timeout> 30 From 5ab813179d8c822cb19209c7c555825f36440e90 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 3 Dec 2010 21:30:32 +0000 Subject: [PATCH 07/67] dont update stats after failure --- .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 14 +++++----- .../net/i2p/i2ptunnel/I2PTunnelServer.java | 26 ++++++++++++------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 1aa6a8347..21c77769a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -138,6 +138,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } else { new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null); } + + long afterHandle = getTunnel().getContext().clock().now(); + long timeToHandle = afterHandle - afterAccept; + getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0); + if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) + _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + + " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } catch (SocketException ex) { try { // Send a 503, so the user doesn't get an HTTP Proxy error message @@ -162,13 +169,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { if (_log.shouldLog(Log.ERROR)) _log.error("OOM in HTTP server", oom); } - - long afterHandle = getTunnel().getContext().clock().now(); - long timeToHandle = afterHandle - afterAccept; - getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0); - if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) - _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + - " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } private static class CompressedRequestor implements Runnable { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 6fc05daa4..bce682eef 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -268,6 +268,14 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { return rv; } + /** + * If usePool is set, this starts the Handler pool and exits. + * Otherwise, this is the accept() loop, and it + * hands each I2P socket to a new thread. + * Note that there is no option to handle each socket in-line, which + * might be appropriate for standard servers that are not filtering headers, + * and are thus unlikely to block. + */ public void run() { I2PServerSocket i2pS_S = sockMgr.getServerSocket(); if (shouldUsePool()) { @@ -300,9 +308,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { public boolean shouldUsePool() { return _usePool; } /** - * minor thread pool to pull off the accept() concurrently. there are still lots - * (and lots) of wasted threads within the I2PTunnelRunner, but its a start - * + * Minor thread pool to pull off the accept() concurrently. + * This is NOT used by default; enable and set number of Handlers + * in the options. */ private class Handler implements Runnable { private I2PServerSocket _serverSocket; @@ -336,6 +344,12 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { Socket s = new Socket(remoteHost, remotePort); afterSocket = I2PAppContext.getGlobalContext().clock().now(); new I2PTunnelRunner(s, socket, slock, null, null); + + long afterHandle = I2PAppContext.getGlobalContext().clock().now(); + long timeToHandle = afterHandle - afterAccept; + if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) + _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + + " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } catch (SocketException ex) { try { socket.close(); @@ -345,12 +359,6 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } catch (IOException ex) { _log.error("Error while waiting for I2PConnections", ex); } - - long afterHandle = I2PAppContext.getGlobalContext().clock().now(); - long timeToHandle = afterHandle - afterAccept; - if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) - _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + - " [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } } From 467b082e82af8a1f4d7f3d4246b7eee013d1224f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 02:03:08 +0000 Subject: [PATCH 08/67] * I2PTunnel: - Limit server blockingHandle threads - Run standard server blockingHandles inline --- .../net/i2p/i2ptunnel/I2PTunnelServer.java | 175 +++++++++++------- 1 file changed, 108 insertions(+), 67 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index bce682eef..ab013f536 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -16,6 +16,12 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.Iterator; import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadFactory; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -47,12 +53,19 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { /** default timeout to 3 minutes - override if desired */ protected long readTimeout = DEFAULT_READ_TIMEOUT; - private static final boolean DEFAULT_USE_POOL = false; + /** do we use threads? default true (ignored for standard servers, always false) */ + private static final String PROP_USE_POOL = "i2ptunnel.usePool"; + private static final boolean DEFAULT_USE_POOL = true; protected static volatile long __serverId = 0; + /** max number of threads - this many slowlorisses will DOS this server, but too high could OOM the JVM */ private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount"; - private static final int DEFAULT_HANDLER_COUNT = 10; - - + private static final int DEFAULT_HANDLER_COUNT = 40; + /** min number of threads */ + private static final int MIN_HANDLERS = 0; + /** how many accept()s to queue waiting for a thread */ + private static final int HANDLER_QUEUE_SIZE = 99; + /** how long to wait before dropping an idle thread */ + private static final long HANDLER_KEEPALIVE_MS = 10*1000; protected I2PTunnelTask task = null; protected boolean bidir = false; @@ -68,7 +81,6 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData)); - setUsePool(tunnel); init(host, port, bais, privData, l); } @@ -80,7 +92,6 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); - setUsePool(tunnel); FileInputStream fis = null; try { fis = new FileInputStream(privkey); @@ -101,19 +112,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super("Server at " + host + ':' + port, notifyThis, tunnel); _log = tunnel.getContext().logManager().getLog(getClass()); - setUsePool(tunnel); init(host, port, privData, privkeyname, l); } - - private void setUsePool(I2PTunnel tunnel) { - String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool"); - if (usePool != null) - _usePool = "true".equalsIgnoreCase(usePool); - else - _usePool = DEFAULT_USE_POOL; - } - private static final int RETRY_DELAY = 20*1000; private static final int MAX_RETRIES = 4; @@ -145,6 +146,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { return; } + // extending classes default to threaded, but for a standard server, we can't get slowlorissed + _usePool = !getClass().equals(I2PTunnelServer.class); + if (_usePool) { + String usePool = getTunnel().getClientOptions().getProperty(PROP_USE_POOL); + if (usePool != null) + _usePool = "true".equalsIgnoreCase(usePool); + else + _usePool = DEFAULT_USE_POOL; + } + // Todo: Can't stop a tunnel from the UI while it's in this loop (no session yet) int retries = 0; while (sockMgr == null) { @@ -201,8 +212,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { * */ public void startRunning() { - Thread t = new I2PAppThread(this); - t.setName("Server " + (++__serverId)); + Thread t = new I2PAppThread(this, "Server " + remoteHost + ':' + remotePort, true); t.start(); } @@ -238,7 +248,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } return false; } - l.log("Stopping tunnels for server at " + getTunnel().listenHost + ':' + this.remotePort); + l.log("Stopping tunnels for server at " + this.remoteHost + ':' + this.remotePort); try { if (i2pss != null) i2pss.close(); getTunnel().removeSession(sockMgr.getSession()); @@ -261,74 +271,105 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { rv = Integer.parseInt(cnt); if (rv <= 0) rv = DEFAULT_HANDLER_COUNT; - } catch (NumberFormatException nfe) { - rv = DEFAULT_HANDLER_COUNT; - } + } catch (NumberFormatException nfe) {} } return rv; } /** - * If usePool is set, this starts the Handler pool and exits. - * Otherwise, this is the accept() loop, and it - * hands each I2P socket to a new thread. - * Note that there is no option to handle each socket in-line, which - * might be appropriate for standard servers that are not filtering headers, - * and are thus unlikely to block. + * If usePool is set, this starts the executor pool. + * Then, do the accept() loop, and either + * hands each I2P socket to the executor or runs it in-line. */ public void run() { I2PServerSocket i2pS_S = sockMgr.getServerSocket(); - if (shouldUsePool()) { - int handlers = getHandlerCount(); - for (int i = 0; i < handlers; i++) { - I2PAppThread handler = new I2PAppThread(new Handler(i2pS_S), "Handle Server " + i); - handler.start(); - } - } else { - while (true) { - try { - final I2PSocket i2ps = i2pS_S.accept(); - if (i2ps == null) throw new I2PException("I2PServerSocket closed"); - new I2PAppThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start(); - } catch (I2PException ipe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe); - return; - } catch (ConnectException ce) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error accepting", ce); - // not killing the server.. - } catch(SocketTimeoutException ste) { - // ignored, we never set the timeout + ThreadPoolExecutor executor = null; + if (_log.shouldLog(Log.WARN)) { + if (_usePool) + _log.warn("Starting executor with " + getHandlerCount() + " threads max"); + else + _log.warn("Threads disabled, running blockingHandles inline"); + } + if (_usePool) { + executor = new CustomThreadPoolExecutor(getHandlerCount(), "ServerHandler pool " + remoteHost + ':' + remotePort); + } + while (open) { + try { + final I2PSocket i2ps = i2pS_S.accept(); + if (i2ps == null) throw new I2PException("I2PServerSocket closed"); + if (_usePool) { + try { + executor.execute(new Handler(i2ps)); + } catch (RejectedExecutionException ree) { + if (open && _log.shouldLog(Log.ERROR)) + _log.error("ServerHandler queue full for " + remoteHost + ':' + remotePort + + "; increase " + PROP_HANDLER_COUNT + '?', ree); + } + } else { + // use only for standard servers that can't get slowlorissed! Not for http or irc + blockingHandle(i2ps); } + } catch (I2PException ipe) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe); + return; + } catch (ConnectException ce) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Error accepting", ce); + // not killing the server.. + try { + Thread.currentThread().sleep(500); + } catch (InterruptedException ie) {} + } catch(SocketTimeoutException ste) { + // ignored, we never set the timeout } } + if (executor != null) + executor.shutdownNow(); } + /** + * Not really needed for now but in case we want to add some hooks like afterExecute(). + * By default, sets up a pool of 0-10 threads with a max queue of 90 and + * an idle time expiration of 5 seconds. + */ + private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { + public CustomThreadPoolExecutor(int max, String name) { + super(MIN_HANDLERS, max, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(HANDLER_QUEUE_SIZE), new CustomThreadFactory(name)); + } + } + + /** just to set the name and set Daemon */ + private static class CustomThreadFactory implements ThreadFactory { + private String _name; + + public CustomThreadFactory(String name) { + _name = name; + } + + public Thread newThread(Runnable r) { + Thread rv = Executors.defaultThreadFactory().newThread(r); + rv.setName(_name); + rv.setDaemon(true); + return rv; + } + } + public boolean shouldUsePool() { return _usePool; } /** - * Minor thread pool to pull off the accept() concurrently. - * This is NOT used by default; enable and set number of Handlers - * in the options. + * Run the blockingHandler. */ private class Handler implements Runnable { - private I2PServerSocket _serverSocket; - public Handler(I2PServerSocket serverSocket) { - _serverSocket = serverSocket; + private I2PSocket _i2ps; + + public Handler(I2PSocket socket) { + _i2ps = socket; } + public void run() { - while (open) { - try { - blockingHandle(_serverSocket.accept()); - } catch (I2PException ex) { - _log.error("Error while waiting for I2PConnections", ex); - return; - } catch (IOException ex) { - _log.error("Error while waiting for I2PConnections", ex); - return; - } - } + blockingHandle(_i2ps); } } From a1c69082e23fe701336768d0bda9149c441cfc91 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:38:11 +0000 Subject: [PATCH 09/67] LBQ -> SynchronousQueue so it will expand the pool --- .../src/net/i2p/i2ptunnel/I2PTunnelServer.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index ab013f536..5427130bc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -17,7 +17,7 @@ import java.net.SocketTimeoutException; import java.util.Iterator; import java.util.Properties; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -59,13 +59,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { protected static volatile long __serverId = 0; /** max number of threads - this many slowlorisses will DOS this server, but too high could OOM the JVM */ private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount"; - private static final int DEFAULT_HANDLER_COUNT = 40; + private static final int DEFAULT_HANDLER_COUNT = 65; /** min number of threads */ private static final int MIN_HANDLERS = 0; - /** how many accept()s to queue waiting for a thread */ - private static final int HANDLER_QUEUE_SIZE = 99; /** how long to wait before dropping an idle thread */ - private static final long HANDLER_KEEPALIVE_MS = 10*1000; + private static final long HANDLER_KEEPALIVE_MS = 30*1000; protected I2PTunnelTask task = null; protected boolean bidir = false; @@ -301,6 +299,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { try { executor.execute(new Handler(i2ps)); } catch (RejectedExecutionException ree) { + try { + i2ps.close(); + } catch (IOException ioe) {} if (open && _log.shouldLog(Log.ERROR)) _log.error("ServerHandler queue full for " + remoteHost + ':' + remotePort + "; increase " + PROP_HANDLER_COUNT + '?', ree); @@ -330,13 +331,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { /** * Not really needed for now but in case we want to add some hooks like afterExecute(). - * By default, sets up a pool of 0-10 threads with a max queue of 90 and - * an idle time expiration of 5 seconds. */ private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(int max, String name) { super(MIN_HANDLERS, max, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(HANDLER_QUEUE_SIZE), new CustomThreadFactory(name)); + new SynchronousQueue(), new CustomThreadFactory(name)); } } From 3a05abe556ccbe8d74f482165f38daab522b77f8 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:41:12 +0000 Subject: [PATCH 10/67] add a stat to see if getBids() is taking too long --- .../src/net/i2p/router/transport/CommSystemFacadeImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 39856f509..094f526c7 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -45,6 +45,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade { _context = context; _log = _context.logManager().getLog(CommSystemFacadeImpl.class); _manager = null; + _context.statManager().createRateStat("transport.getBidsJobTime", "How long does it take?", "Transport", new long[] { 10*60*1000l }); startGeoIP(); } @@ -131,7 +132,9 @@ public class CommSystemFacadeImpl extends CommSystemFacade { public void processMessage(OutNetMessage msg) { //GetBidsJob j = new GetBidsJob(_context, this, msg); //j.runJob(); + long before = _context.clock().now(); GetBidsJob.getBids(_context, this, msg); + _context.statManager().addRateData("transport.getBidsJobTime", _context.clock().now() - before, 0); } @Override From 947010ad013da3f370103b840a78fb9ff181ef97 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:42:40 +0000 Subject: [PATCH 11/67] I2PThread -> I2PAppThread --- core/java/src/net/i2p/client/I2PSessionImpl.java | 11 +++-------- core/java/src/net/i2p/client/I2PSimpleSession.java | 6 ++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 4db4e1759..a197a8209 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -42,7 +42,7 @@ import net.i2p.data.i2cp.SessionId; import net.i2p.internal.I2CPMessageQueue; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; -import net.i2p.util.I2PThread; +import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -283,9 +283,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa setOpening(true); _closed = false; _availabilityNotifier.stopNotifying(); - I2PThread notifier = new I2PThread(_availabilityNotifier); - notifier.setName("Notifier " + _myDestination.calculateHash().toBase64().substring(0,4)); - notifier.setDaemon(true); + Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true); notifier.start(); if ( (_options != null) && @@ -760,11 +758,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa buf.append(s); else buf.append(getClass().getSimpleName()); - buf.append(" #"); if (_sessionId != null) - buf.append(_sessionId.getSessionId()); - else - buf.append("n/a"); + buf.append(" #").append(_sessionId.getSessionId()); buf.append("]: "); return buf.toString(); } diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index c2da90217..06f31443a 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -22,7 +22,7 @@ import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.internal.I2CPMessageQueue; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; -import net.i2p.util.I2PThread; +import net.i2p.util.I2PAppThread; import net.i2p.util.InternalSocket; /** @@ -69,9 +69,7 @@ class I2PSimpleSession extends I2PSessionImpl2 { public void connect() throws I2PSessionException { _closed = false; _availabilityNotifier.stopNotifying(); - I2PThread notifier = new I2PThread(_availabilityNotifier); - notifier.setName("Simple Notifier"); - notifier.setDaemon(true); + Thread notifier = new I2PAppThread(_availabilityNotifier, "Simple Notifier", true); notifier.start(); try { From 04ea1fb9ca4dfe66d477d17c429faa49ed00a35d Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:43:30 +0000 Subject: [PATCH 12/67] javadoc --- .../java/src/net/i2p/client/streaming/I2PServerSocket.java | 5 +++-- .../src/net/i2p/client/streaming/I2PServerSocketFull.java | 3 ++- .../src/net/i2p/client/streaming/I2PSocketManagerFull.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index d0028fdb8..9f43aa246 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -19,9 +19,10 @@ public interface I2PServerSocket { /** * Waits for the next socket connecting. If a remote user tried to make a * connection and the local application wasn't .accept()ing new connections, - * they should get refused (if .accept() doesnt occur in some small period) + * they should get refused (if .accept() doesnt occur in some small period). + * Warning - unlike regular ServerSocket, may return null. * - * @return a connected I2PSocket + * @return a connected I2PSocket OR NULL * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java index 262b49624..acb58fe15 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java @@ -15,8 +15,9 @@ class I2PServerSocketFull implements I2PServerSocket { } /** + * Warning, unlike regular ServerSocket, may return null * - * @return I2PSocket + * @return I2PSocket OR NULL * @throws net.i2p.I2PException * @throws SocketTimeoutException */ diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java index f71067698..d93fe0445 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java @@ -112,7 +112,7 @@ class I2PSocketManagerFull implements I2PSocketManager { /** * - * @return connected I2PSocket + * @return connected I2PSocket OR NULL * @throws net.i2p.I2PException * @throws java.net.SocketTimeoutException */ From eadf472dd085271f688fb2f321e5e978e44006ea Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:47:22 +0000 Subject: [PATCH 13/67] * I2PTunnel: - For clients, use a common thread pool that expires idle threads rather than keeping 5 accept() threads for each client. This also removes the configurable (30s default) max wait time for a socket, this may have to be restored. - Use pool for HTTP decompression also. --- .../i2ptunnel/HTTPResponseOutputStream.java | 11 +- .../i2p/i2ptunnel/I2PTunnelClientBase.java | 191 +++++------------- 2 files changed, 61 insertions(+), 141 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java index 890a4f141..7fc808643 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java @@ -16,6 +16,7 @@ import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.zip.GZIPInputStream; +import java.util.concurrent.RejectedExecutionException; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; @@ -228,7 +229,15 @@ class HTTPResponseOutputStream extends FilterOutputStream { //out.flush(); PipedInputStream pi = new PipedInputStream(); PipedOutputStream po = new PipedOutputStream(pi); - new I2PAppThread(new Pusher(pi, out), "HTTP decompressor").start(); + // Run in the client thread pool, as there should be an unused thread + // there after the accept(). + // Overridden in I2PTunnelHTTPServer, where it does not use the client pool. + try { + I2PTunnelClientBase._executor.execute(new Pusher(pi, out)); + } catch (RejectedExecutionException ree) { + // shouldn't happen + throw ree; + } out = po; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index 2dcb2af15..af89cb2a2 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -17,6 +17,13 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadFactory; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -64,35 +71,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna private String handlerName; private String privKeyFile; - // private Object conLock = new Object(); - - /** List of Socket for those accept()ed but not yet started up */ - protected final List _waitingSockets = new ArrayList(4); // FIXME should be final and use a factory. FIXME - /** How many connections will we allow to be in the process of being built at once? */ - private int _numConnectionBuilders; - /** How long will we allow sockets to sit in the _waitingSockets map before killing them? */ - private int _maxWaitTime; - - /** - * How many concurrent connections this I2PTunnel instance will allow to be - * in the process of connecting (or if less than 1, there is no limit)? - */ - public static final String PROP_NUM_CONNECTION_BUILDERS = "i2ptunnel.numConnectionBuilders"; - /** - * How long will we let a socket wait after being accept()ed without getting - * pumped through a connection builder (in milliseconds). If this time is - * reached, the socket is unceremoniously closed and discarded. If the max - * wait time is less than 1, there is no limit. - * - */ - public static final String PROP_MAX_WAIT_TIME = "i2ptunnel.maxWaitTime"; - - private static final int DEFAULT_NUM_CONNECTION_BUILDERS = 5; - private static final int DEFAULT_MAX_WAIT_TIME = 30*1000; - // true if we are chained from a server. private boolean chained = false; + /** how long to wait before dropping an idle thread */ + private static final long HANDLER_KEEPALIVE_MS = 2*60*1000; + + /** + * We keep a static pool of socket handlers for all clients, + * as there is no need for isolation on the client side. + * Extending classes may use it for other purposes. + * Not for use by servers, as there is no limit on threads. + */ + static final Executor _executor; + private static int _executorThreadCount; + static { + _executor = new CustomThreadPoolExecutor(); + } + public I2PTunnelClientBase(int localPort, Logging l, I2PSocketManager sktMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId ) throws IllegalArgumentException { @@ -111,8 +107,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna _context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _log = _context.logManager().getLog(getClass()); - Thread t = new I2PAppThread(this); - t.setName("Client " + _clientId); + Thread t = new I2PAppThread(this, "Client " + tunnel.listenHost + ':' + localPort); listenerReady = false; t.start(); open = true; @@ -126,8 +121,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - configurePool(tunnel); - if (open && listenerReady) { l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort); notifyEvent("openBaseClientResult", "ok"); @@ -136,6 +129,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna notifyEvent("openBaseClientResult", "error"); } } + public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel) throws IllegalArgumentException { @@ -212,8 +206,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - configurePool(tunnel); - if (open && listenerReady) { if (openNow) l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort); @@ -226,37 +218,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - /** - * build and configure the pool handling accept()ed but not yet - * established connections - * - */ - private void configurePool(I2PTunnel tunnel) { - //_waitingSockets = new ArrayList(4); - - Properties opts = tunnel.getClientOptions(); - String maxWait = opts.getProperty(PROP_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME+""); - try { - _maxWaitTime = Integer.parseInt(maxWait); - } catch (NumberFormatException nfe) { - _maxWaitTime = DEFAULT_MAX_WAIT_TIME; - } - - String numBuild = opts.getProperty(PROP_NUM_CONNECTION_BUILDERS, DEFAULT_NUM_CONNECTION_BUILDERS+""); - try { - _numConnectionBuilders = Integer.parseInt(numBuild); - } catch (NumberFormatException nfe) { - _numConnectionBuilders = DEFAULT_NUM_CONNECTION_BUILDERS; - } - - for (int i = 0; i < _numConnectionBuilders; i++) { - String name = "ClientBuilder" + _clientId + '.' + i; - I2PAppThread b = new I2PAppThread(new TunnelConnectionBuilder(), name); - b.setDaemon(true); - b.start(); - } - } - /** * Sets the this.sockMgr field if it is null, or if we want a new one * @@ -543,7 +504,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna synchronized (this) { notifyAll(); } - synchronized (_waitingSockets) { _waitingSockets.notifyAll(); } return; } ss = new ServerSocket(localPort, 0, addr); @@ -572,12 +532,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - while (true) { + while (open) { Socket s = ss.accept(); - long before = System.currentTimeMillis(); manageConnection(s); - long total = System.currentTimeMillis() - before; - _context.statManager().addRateData("i2ptunnel.client.manageTime", total, total); } } catch (IOException ex) { if (open) { @@ -592,9 +549,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna notifyAll(); } } - synchronized (_waitingSockets) { - _waitingSockets.notifyAll(); - } } /** @@ -604,24 +558,38 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna */ protected void manageConnection(Socket s) { if (s == null) return; - if (_numConnectionBuilders <= 0) { - new I2PAppThread(new BlockingRunner(s), "Clinet run").start(); - return; + try { + _executor.execute(new BlockingRunner(s)); + } catch (RejectedExecutionException ree) { + // should never happen, we have an unbounded pool and never stop the executor + try { + s.close(); + } catch (IOException ioe) {} } - - if (_maxWaitTime > 0) - SimpleScheduler.getInstance().addEvent(new CloseEvent(s), _maxWaitTime); + } - synchronized (_waitingSockets) { - _waitingSockets.add(s); - _waitingSockets.notifyAll(); + /** + * Not really needed for now but in case we want to add some hooks like afterExecute(). + */ + private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { + public CustomThreadPoolExecutor() { + super(0, Integer.MAX_VALUE, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS, + new SynchronousQueue(), new CustomThreadFactory()); + } + } + + /** just to set the name and set Daemon */ + private static class CustomThreadFactory implements ThreadFactory { + public Thread newThread(Runnable r) { + Thread rv = Executors.defaultThreadFactory().newThread(r); + rv.setName("I2PTunnel Client Runner " + (++_executorThreadCount)); + rv.setDaemon(true); + return rv; } } /** - * Blocking runner, used during the connection establishment whenever we - * are not using the queued builders. - * + * Blocking runner, used during the connection establishment */ private class BlockingRunner implements Runnable { private Socket _s; @@ -631,32 +599,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - /** - * Remove and close the socket from the waiting list, if it is still there. - * - */ - private class CloseEvent implements SimpleTimer.TimedEvent { - private Socket _s; - public CloseEvent(Socket s) { _s = s; } - public void timeReached() { - int remaining = 0; - boolean stillWaiting = false; - synchronized (_waitingSockets) { - stillWaiting = _waitingSockets.remove(_s); - remaining = _waitingSockets.size(); - } - if (stillWaiting) { - try { _s.close(); } catch (IOException ioe) {} - if (_log.shouldLog(Log.INFO)) { - _context.statManager().addRateData("i2ptunnel.client.closeBacklog", remaining, 0); - _log.info("Closed a waiting socket because of backlog"); - } - } else { - _context.statManager().addRateData("i2ptunnel.client.closeNoBacklog", remaining, 0); - } - } - } - public boolean close(boolean forced) { if (_log.shouldLog(Log.INFO)) _log.info("close() called: forced = " + forced + " open = " + open + " sockMgr = " + sockMgr); @@ -694,7 +636,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna //l.log("Client closed."); } - synchronized (_waitingSockets) { _waitingSockets.notifyAll(); } return true; } @@ -706,36 +647,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } - /** - * Pool runner pulling sockets off the waiting list and pushing them - * through clientConnectionRun. This dies when the I2PTunnel instance - * is closed. - * - */ - private class TunnelConnectionBuilder implements Runnable { - public void run() { - Socket s = null; - while (open) { - try { - synchronized (_waitingSockets) { - if (_waitingSockets.isEmpty()) - _waitingSockets.wait(); - else - s = (Socket)_waitingSockets.remove(0); - } - } catch (InterruptedException ie) {} - - if (s != null) { - long before = System.currentTimeMillis(); - clientConnectionRun(s); - long total = System.currentTimeMillis() - before; - _context.statManager().addRateData("i2ptunnel.client.buildRunTime", total, 0); - } - s = null; - } - } - } - /** * Manage a connection in a separate thread. This only works if * you do not override manageConnection() From 6dfd9bca696d5de1f54e27227df7a648d9139c6b Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:48:01 +0000 Subject: [PATCH 14/67] log tweaks --- core/java/src/net/i2p/util/I2PThread.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/java/src/net/i2p/util/I2PThread.java b/core/java/src/net/i2p/util/I2PThread.java index c21c66f6b..9b76b8fc9 100644 --- a/core/java/src/net/i2p/util/I2PThread.java +++ b/core/java/src/net/i2p/util/I2PThread.java @@ -61,8 +61,8 @@ public class I2PThread extends Thread { _createdBy = new Exception("Created by"); } - private void log(int level, String msg) { log(level, msg, null); } - private void log(int level, String msg, Throwable t) { + private static void log(int level, String msg) { log(level, msg, null); } + private static void log(int level, String msg, Throwable t) { // we cant assume log is created if (_log == null) _log = new Log(I2PThread.class); if (_log.shouldLog(level)) @@ -72,12 +72,12 @@ public class I2PThread extends Thread { @Override public void run() { _name = Thread.currentThread().getName(); - log(Log.DEBUG, "New thread started: " + _name, _createdBy); + log(Log.INFO, "New thread started" + (isDaemon() ? " (daemon): " : ": ") + _name, _createdBy); try { super.run(); } catch (Throwable t) { try { - log(Log.CRIT, "Killing thread " + getName(), t); + log(Log.CRIT, "Thread terminated unexpectedly: " + getName(), t); } catch (Throwable woof) { System.err.println("Died within the OOM itself"); t.printStackTrace(); @@ -85,12 +85,12 @@ public class I2PThread extends Thread { if (t instanceof OutOfMemoryError) fireOOM((OutOfMemoryError)t); } - log(Log.DEBUG, "Thread finished gracefully: " + _name); + log(Log.INFO, "Thread finished normally: " + _name); } @Override protected void finalize() throws Throwable { - log(Log.DEBUG, "Thread finalized: " + _name); + //log(Log.DEBUG, "Thread finalized: " + _name); super.finalize(); } From 9aaf95ca98e9652c84ed2254c1bfc01a8590156c Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 18:54:06 +0000 Subject: [PATCH 15/67] * Threads: - Reduce thread pool sizes based on memory and/or bandwidth limits - Tweak some thread names for clarity --- .../src/org/klomp/snark/PeerCoordinator.java | 2 +- .../src/net/i2p/util/SimpleScheduler.java | 10 +++-- core/java/src/net/i2p/util/SimpleTimer.java | 12 ++++-- core/java/src/net/i2p/util/SimpleTimer2.java | 10 +++-- router/java/src/net/i2p/router/JobQueue.java | 4 +- .../transport/ntcp/NTCPSendFinisher.java | 26 +++++++----- .../router/transport/ntcp/NTCPTransport.java | 40 +++++++++++++------ .../net/i2p/router/transport/ntcp/Reader.java | 14 +++---- .../net/i2p/router/transport/ntcp/Writer.java | 14 +++---- .../router/transport/udp/MessageReceiver.java | 25 ++++++++---- .../router/transport/udp/PacketHandler.java | 28 +++++++++---- .../router/tunnel/TunnelGatewayPumper.java | 12 ++++-- 12 files changed, 129 insertions(+), 68 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 2a6eb79af..bebc4588f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -428,7 +428,7 @@ public class PeerCoordinator implements PeerListener peer.runConnection(_util, listener, bitfield); } }; - String threadName = peer.toString(); + String threadName = "Snark peer " + peer.toString(); new I2PAppThread(r, threadName).start(); return true; } diff --git a/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java index ee7d36e99..2b1ddf44f 100644 --- a/core/java/src/net/i2p/util/SimpleScheduler.java +++ b/core/java/src/net/i2p/util/SimpleScheduler.java @@ -28,12 +28,14 @@ import net.i2p.I2PAppContext; public class SimpleScheduler { private static final SimpleScheduler _instance = new SimpleScheduler(); public static SimpleScheduler getInstance() { return _instance; } - private static final int THREADS = 4; + private static final int MIN_THREADS = 2; + private static final int MAX_THREADS = 4; private I2PAppContext _context; private Log _log; private ScheduledThreadPoolExecutor _executor; private String _name; private int _count; + private final int _threads; protected SimpleScheduler() { this("SimpleScheduler"); } protected SimpleScheduler(String name) { @@ -41,7 +43,9 @@ public class SimpleScheduler { _log = _context.logManager().getLog(SimpleScheduler.class); _name = name; _count = 0; - _executor = new ScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory()); + long maxMemory = Runtime.getRuntime().maxMemory(); + _threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024)))); + _executor = new ScheduledThreadPoolExecutor(_threads, new CustomThreadFactory()); _executor.prestartAllCoreThreads(); } @@ -90,7 +94,7 @@ public class SimpleScheduler { private class CustomThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread rv = Executors.defaultThreadFactory().newThread(r); - rv.setName(_name + ' ' + (++_count) + '/' + THREADS); + rv.setName(_name + ' ' + (++_count) + '/' + _threads); // Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates! // String name = rv.getThreadGroup().getName(); // if(!name.equals("main")) { diff --git a/core/java/src/net/i2p/util/SimpleTimer.java b/core/java/src/net/i2p/util/SimpleTimer.java index 6a8b855e3..0b5430711 100644 --- a/core/java/src/net/i2p/util/SimpleTimer.java +++ b/core/java/src/net/i2p/util/SimpleTimer.java @@ -18,14 +18,16 @@ import net.i2p.I2PAppContext; public class SimpleTimer { private static final SimpleTimer _instance = new SimpleTimer(); public static SimpleTimer getInstance() { return _instance; } - private I2PAppContext _context; - private Log _log; + private final I2PAppContext _context; + private final Log _log; /** event time (Long) to event (TimedEvent) mapping */ private final TreeMap _events; /** event (TimedEvent) to event time (Long) mapping */ private Map _eventTimes; private final List _readyEvents; private SimpleStore runn; + private static final int MIN_THREADS = 2; + private static final int MAX_THREADS = 4; protected SimpleTimer() { this("SimpleTimer"); } protected SimpleTimer(String name) { @@ -39,9 +41,11 @@ public class SimpleTimer { runner.setName(name); runner.setDaemon(true); runner.start(); - for (int i = 0; i < 3; i++) { + long maxMemory = Runtime.getRuntime().maxMemory(); + int threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024)))); + for (int i = 1; i <= threads ; i++) { I2PThread executor = new I2PThread(new Executor(_context, _log, _readyEvents, runn)); - executor.setName(name + "Executor " + i); + executor.setName(name + "Executor " + i + '/' + threads); executor.setDaemon(true); executor.start(); } diff --git a/core/java/src/net/i2p/util/SimpleTimer2.java b/core/java/src/net/i2p/util/SimpleTimer2.java index b2af33cf2..bda41e621 100644 --- a/core/java/src/net/i2p/util/SimpleTimer2.java +++ b/core/java/src/net/i2p/util/SimpleTimer2.java @@ -27,12 +27,14 @@ import net.i2p.I2PAppContext; public class SimpleTimer2 { private static final SimpleTimer2 _instance = new SimpleTimer2(); public static SimpleTimer2 getInstance() { return _instance; } - private static final int THREADS = 4; + private static final int MIN_THREADS = 2; + private static final int MAX_THREADS = 4; private I2PAppContext _context; private static Log _log; // static so TimedEvent can use it private ScheduledThreadPoolExecutor _executor; private String _name; private int _count; + private final int _threads; protected SimpleTimer2() { this("SimpleTimer2"); } protected SimpleTimer2(String name) { @@ -40,7 +42,9 @@ public class SimpleTimer2 { _log = _context.logManager().getLog(SimpleTimer2.class); _name = name; _count = 0; - _executor = new CustomScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory()); + long maxMemory = Runtime.getRuntime().maxMemory(); + _threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024)))); + _executor = new CustomScheduledThreadPoolExecutor(_threads, new CustomThreadFactory()); _executor.prestartAllCoreThreads(); } @@ -67,7 +71,7 @@ public class SimpleTimer2 { private class CustomThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread rv = Executors.defaultThreadFactory().newThread(r); - rv.setName(_name + ' ' + (++_count) + '/' + THREADS); + rv.setName(_name + ' ' + (++_count) + '/' + _threads); // Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates! // String name = rv.getThreadGroup().getName(); // if(!name.equals("main")) { diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index a8b5395b0..42ddadf5a 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -395,10 +395,8 @@ public class JobQueue { for (int i = _queueRunners.size(); i < numThreads; i++) { JobQueueRunner runner = new JobQueueRunner(_context, i); _queueRunners.put(Integer.valueOf(i), runner); - Thread t = new I2PThread(runner); - t.setName("JobQueue"+(_runnerId++)); + Thread t = new I2PThread(runner, "JobQueue " + (++_runnerId) + '/' + numThreads, false); //t.setPriority(I2PThread.MAX_PRIORITY-1); - t.setDaemon(false); t.start(); } } else if (_queueRunners.size() == numThreads) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java index fd5cf1ac9..374c7a5ba 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java @@ -24,22 +24,27 @@ import net.i2p.util.Log; * @author zzz */ public class NTCPSendFinisher { - private static final int THREADS = 4; + private static final int MIN_THREADS = 1; + private static final int MAX_THREADS = 4; private final I2PAppContext _context; private final NTCPTransport _transport; private final Log _log; - private int _count; + private static int _count; private ThreadPoolExecutor _executor; + private static int _threads; public NTCPSendFinisher(I2PAppContext context, NTCPTransport transport) { _context = context; _log = _context.logManager().getLog(NTCPSendFinisher.class); _transport = transport; + _context.statManager().createRateStat("ntcp.sendFinishTime", "How long to queue and excecute msg.afterSend()", "ntcp", new long[] {5*1000}); } public void start() { _count = 0; - _executor = new CustomThreadPoolExecutor(); + long maxMemory = Runtime.getRuntime().maxMemory(); + _threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024)))); + _executor = new CustomThreadPoolExecutor(_threads); } public void stop() { @@ -57,18 +62,18 @@ public class NTCPSendFinisher { } // not really needed for now but in case we want to add some hooks like afterExecute() - private class CustomThreadPoolExecutor extends ThreadPoolExecutor { - public CustomThreadPoolExecutor() { + private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { + public CustomThreadPoolExecutor(int num) { // use unbounded queue, so maximumPoolSize and keepAliveTime have no effect - super(THREADS, THREADS, 1000, TimeUnit.MILLISECONDS, + super(num, num, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new CustomThreadFactory()); } } - private class CustomThreadFactory implements ThreadFactory { + private static class CustomThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread rv = Executors.defaultThreadFactory().newThread(r); - rv.setName("NTCPSendFinisher " + (++_count) + '/' + THREADS); + rv.setName("NTCPSendFinisher " + (++_count) + '/' + _threads); rv.setDaemon(true); return rv; } @@ -78,15 +83,18 @@ public class NTCPSendFinisher { * Call afterSend() for the message */ private class RunnableEvent implements Runnable { - private OutNetMessage _msg; + private final OutNetMessage _msg; + private final long _queued; public RunnableEvent(OutNetMessage msg) { _msg = msg; + _queued = _context.clock().now(); } public void run() { try { _transport.afterSend(_msg, true, false, _msg.getSendTime()); + _context.statManager().addRateData("ntcp.sendFinishTime", _context.clock().now() - _queued, 0); } catch (Throwable t) { _log.log(Log.CRIT, " wtf, afterSend borked", t); } 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 3d6d91f51..31060ba41 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -433,8 +433,10 @@ public class NTCPTransport extends TransportImpl { return skews; } - private static final int NUM_CONCURRENT_READERS = 3; - private static final int NUM_CONCURRENT_WRITERS = 3; + private static final int MIN_CONCURRENT_READERS = 2; // unless < 32MB + private static final int MIN_CONCURRENT_WRITERS = 2; // unless < 32MB + private static final int MAX_CONCURRENT_READERS = 4; + private static final int MAX_CONCURRENT_WRITERS = 4; /** * Called by TransportManager. @@ -449,12 +451,8 @@ public class NTCPTransport extends TransportImpl { if (_pumper.isAlive()) return _myAddress != null ? _myAddress.toRouterAddress() : null; if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening"); - _finisher.start(); - _pumper.startPumping(); - - _reader.startReading(NUM_CONCURRENT_READERS); - _writer.startWriting(NUM_CONCURRENT_WRITERS); + startIt(); configureLocalAddress(); return bindAddress(); } @@ -471,12 +469,8 @@ public class NTCPTransport extends TransportImpl { if (_pumper.isAlive()) return _myAddress != null ? _myAddress.toRouterAddress() : null; if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening"); - _finisher.start(); - _pumper.startPumping(); - - _reader.startReading(NUM_CONCURRENT_READERS); - _writer.startWriting(NUM_CONCURRENT_WRITERS); + startIt(); if (addr == null) _myAddress = null; else @@ -484,6 +478,28 @@ public class NTCPTransport extends TransportImpl { return bindAddress(); } + /** + * Start up. Caller must synchronize. + * @since 0.8.3 + */ + private void startIt() { + _finisher.start(); + _pumper.startPumping(); + + long maxMemory = Runtime.getRuntime().maxMemory(); + int nr, nw; + if (maxMemory < 32*1024*1024) { + nr = nw = 1; + } else if (maxMemory < 64*1024*1024) { + nr = nw = 2; + } else { + nr = Math.max(MIN_CONCURRENT_READERS, Math.min(MAX_CONCURRENT_READERS, _context.bandwidthLimiter().getInboundKBytesPerSecond() / 20)); + nw = Math.max(MIN_CONCURRENT_WRITERS, Math.min(MAX_CONCURRENT_WRITERS, _context.bandwidthLimiter().getOutboundKBytesPerSecond() / 20)); + } + _reader.startReading(nr); + _writer.startWriting(nw); + } + public boolean isAlive() { return _pumper.isAlive(); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/Reader.java b/router/java/src/net/i2p/router/transport/ntcp/Reader.java index c1029b26e..969481545 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/Reader.java +++ b/router/java/src/net/i2p/router/transport/ntcp/Reader.java @@ -15,13 +15,13 @@ import net.i2p.util.Log; * */ class Reader { - private RouterContext _context; - private Log _log; + private final RouterContext _context; + private final Log _log; // TODO change to LBQ ?? private final List<NTCPConnection> _pendingConnections; - private List<NTCPConnection> _liveReads; - private List<NTCPConnection> _readAfterLive; - private List<Runner> _runners; + private final List<NTCPConnection> _liveReads; + private final List<NTCPConnection> _readAfterLive; + private final List<Runner> _runners; public Reader(RouterContext ctx) { _context = ctx; @@ -33,9 +33,9 @@ class Reader { } public void startReading(int numReaders) { - for (int i = 0; i < numReaders; i++) { + for (int i = 1; i <= numReaders; i++) { Runner r = new Runner(); - I2PThread t = new I2PThread(r, "NTCP read " + i, true); + I2PThread t = new I2PThread(r, "NTCP reader " + i + '/' + numReaders, true); _runners.add(r); t.start(); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/Writer.java b/router/java/src/net/i2p/router/transport/ntcp/Writer.java index ca676c572..260569df7 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/Writer.java +++ b/router/java/src/net/i2p/router/transport/ntcp/Writer.java @@ -14,12 +14,12 @@ import net.i2p.util.Log; * */ class Writer { - private RouterContext _context; - private Log _log; + private final RouterContext _context; + private final Log _log; private final List<NTCPConnection> _pendingConnections; - private List<NTCPConnection> _liveWrites; - private List<NTCPConnection> _writeAfterLive; - private List<Runner> _runners; + private final List<NTCPConnection> _liveWrites; + private final List<NTCPConnection> _writeAfterLive; + private final List<Runner> _runners; public Writer(RouterContext ctx) { _context = ctx; @@ -31,9 +31,9 @@ class Writer { } public void startWriting(int numWriters) { - for (int i = 0; i < numWriters; i++) { + for (int i = 1; i <=numWriters; i++) { Runner r = new Runner(); - I2PThread t = new I2PThread(r, "NTCP write " + i, true); + I2PThread t = new I2PThread(r, "NTCP writer " + i + '/' + numWriters, true); _runners.add(r); t.start(); } diff --git a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java index 08b6088c4..4988a0685 100644 --- a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java @@ -27,7 +27,9 @@ class MessageReceiver { private final BlockingQueue<InboundMessageState> _completeMessages; private boolean _alive; //private ByteCache _cache; - private static final int THREADS = 5; + private static final int MIN_THREADS = 2; // unless < 32MB + private static final int MAX_THREADS = 5; + private final int _threadCount; private static final long POISON_IMS = -99999999999l; public MessageReceiver(RouterContext ctx, UDPTransport transport) { @@ -35,10 +37,19 @@ class MessageReceiver { _log = ctx.logManager().getLog(MessageReceiver.class); _transport = transport; _completeMessages = new LinkedBlockingQueue(); + + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory < 32*1024*1024) + _threadCount = 1; + else if (maxMemory < 64*1024*1024) + _threadCount = 2; + else + _threadCount = Math.max(MIN_THREADS, Math.min(MAX_THREADS, ctx.bandwidthLimiter().getInboundKBytesPerSecond() / 20)); + // the runners run forever, no need to have a cache //_cache = ByteCache.getInstance(64, I2NPMessage.MAX_SIZE); _context.statManager().createRateStat("udp.inboundExpired", "How many messages were expired before reception?", "udp", UDPTransport.RATES); - _context.statManager().createRateStat("udp.inboundRemaining", "How many messages were remaining when a message is pulled off the complete queue?", "udp", UDPTransport.RATES); + //_context.statManager().createRateStat("udp.inboundRemaining", "How many messages were remaining when a message is pulled off the complete queue?", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.inboundReady", "How many messages were ready when a message is added to the complete queue?", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.inboundReadTime", "How long it takes to parse in the completed fragments into a message?", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.inboundReceiveProcessTime", "How long it takes to add the message to the transport?", "udp", UDPTransport.RATES); @@ -49,8 +60,8 @@ class MessageReceiver { public void startup() { _alive = true; - for (int i = 0; i < THREADS; i++) { - I2PThread t = new I2PThread(new Runner(), "UDP message receiver " + i + '/' + THREADS, true); + for (int i = 0; i < _threadCount; i++) { + I2PThread t = new I2PThread(new Runner(), "UDP message receiver " + (i+1) + '/' + _threadCount, true); t.start(); } } @@ -64,7 +75,7 @@ class MessageReceiver { public void shutdown() { _alive = false; _completeMessages.clear(); - for (int i = 0; i < THREADS; i++) { + for (int i = 0; i < _threadCount; i++) { InboundMessageState ims = new InboundMessageState(_context, POISON_IMS, null); _completeMessages.offer(ims); } @@ -119,8 +130,8 @@ class MessageReceiver { if (message != null) { long before = System.currentTimeMillis(); - if (remaining > 0) - _context.statManager().addRateData("udp.inboundRemaining", remaining, 0); + //if (remaining > 0) + // _context.statManager().addRateData("udp.inboundRemaining", remaining, 0); int size = message.getCompleteSize(); if (_log.shouldLog(Log.INFO)) _log.info("Full message received (" + message.getMessageId() + ") after " + message.getLifetime()); diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java index d130a4c83..2c0138228 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -31,11 +31,13 @@ class PacketHandler { private boolean _keepReading; private final Handler[] _handlers; - private static final int NUM_HANDLERS = 5; + private static final int MIN_NUM_HANDLERS = 2; // unless < 32MB + private static final int MAX_NUM_HANDLERS = 5; /** let packets be up to 30s slow */ private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000; - PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) {// LINT -- Exporting non-public type through public API + PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, + InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) { _context = ctx; _log = ctx.logManager().getLog(PacketHandler.class); _transport = transport; @@ -44,10 +46,20 @@ class PacketHandler { _inbound = inbound; _testManager = testManager; _introManager = introManager; - _handlers = new Handler[NUM_HANDLERS]; - for (int i = 0; i < NUM_HANDLERS; i++) { + + long maxMemory = Runtime.getRuntime().maxMemory(); + int num_handlers; + if (maxMemory < 32*1024*1024) + num_handlers = 1; + else if (maxMemory < 64*1024*1024) + num_handlers = 2; + else + num_handlers = Math.max(MIN_NUM_HANDLERS, Math.min(MAX_NUM_HANDLERS, ctx.bandwidthLimiter().getInboundKBytesPerSecond() / 20)); + _handlers = new Handler[num_handlers]; + for (int i = 0; i < num_handlers; i++) { _handlers[i] = new Handler(); } + _context.statManager().createRateStat("udp.handleTime", "How long it takes to handle a received packet after its been pulled off the queue", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.queueTime", "How long after a packet is received can we begin handling it", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.receivePacketSkew", "How long ago after the packet was sent did we receive it", "udp", UDPTransport.RATES); @@ -79,8 +91,8 @@ class PacketHandler { public void startup() { _keepReading = true; - for (int i = 0; i < NUM_HANDLERS; i++) { - I2PThread t = new I2PThread(_handlers[i], "UDP Packet handler " + i + '/' + NUM_HANDLERS, true); + for (int i = 0; i < _handlers.length; i++) { + I2PThread t = new I2PThread(_handlers[i], "UDP Packet handler " + (i+1) + '/' + _handlers.length, true); t.start(); } } @@ -91,8 +103,8 @@ class PacketHandler { String getHandlerStatus() { StringBuilder rv = new StringBuilder(); - rv.append("Handlers: ").append(NUM_HANDLERS); - for (int i = 0; i < NUM_HANDLERS; i++) { + rv.append("Handlers: ").append(_handlers.length); + for (int i = 0; i < _handlers.length; i++) { Handler handler = _handlers[i]; rv.append(" handler ").append(i).append(" state: ").append(handler._state); } diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java index 05db0b0ce..7f29f5743 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java @@ -16,22 +16,26 @@ public class TunnelGatewayPumper implements Runnable { private RouterContext _context; private final BlockingQueue<PumpedTunnelGateway> _wantsPumping; private boolean _stop; - private static final int PUMPERS = 4; + private static final int MIN_PUMPERS = 1; + private static final int MAX_PUMPERS = 4; + private final int _pumpers; /** Creates a new instance of TunnelGatewayPumper */ public TunnelGatewayPumper(RouterContext ctx) { _context = ctx; _wantsPumping = new LinkedBlockingQueue(); _stop = false; - for (int i = 0; i < PUMPERS; i++) - new I2PThread(this, "Tunnel GW pumper " + i + '/' + PUMPERS, true).start(); + long maxMemory = Runtime.getRuntime().maxMemory(); + _pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024)))); + for (int i = 0; i < _pumpers; i++) + new I2PThread(this, "Tunnel GW pumper " + (i+1) + '/' + _pumpers, true).start(); } public void stopPumping() { _stop=true; _wantsPumping.clear(); PumpedTunnelGateway poison = new PoisonPTG(_context); - for (int i = 0; i < PUMPERS; i++) + for (int i = 0; i < _pumpers; i++) _wantsPumping.offer(poison); for (int i = 1; i <= 5 && !_wantsPumping.isEmpty(); i++) { try { From 7c583fb1e6b8eef5c69a3a6ab0762e73da3eeeef Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 20:34:57 +0000 Subject: [PATCH 16/67] convert mark router liveliness thread to SimpleScheduler --- router/java/src/net/i2p/router/Router.java | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 7f8b2788e..5904d04ea 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -1281,11 +1281,7 @@ public class Router { */ private void beginMarkingLiveliness() { File f = getPingFile(); - // not an I2PThread for context creation issues - Thread t = new Thread(new MarkLiveliness(_context, this, f)); - t.setName("Mark router liveliness"); - t.setDaemon(true); - t.start(); + SimpleScheduler.getInstance().addPeriodicEvent(new MarkLiveliness(this, f), 0, LIVELINESS_DELAY); } public static final String PROP_BANDWIDTH_SHARE_PERCENTAGE = "router.sharePercentage"; @@ -1523,22 +1519,24 @@ private static class UpdateRoutingKeyModifierJob extends JobImpl { } } -private static class MarkLiveliness implements Runnable { - private RouterContext _context; +/** + * Write a timestamp to the ping file where the wrapper can see it + */ +private static class MarkLiveliness implements SimpleTimer.TimedEvent { private Router _router; private File _pingFile; - public MarkLiveliness(RouterContext ctx, Router router, File pingFile) { - _context = ctx; + + public MarkLiveliness(Router router, File pingFile) { _router = router; _pingFile = pingFile; - } - public void run() { _pingFile.deleteOnExit(); - do { + } + + public void timeReached() { + if (_router.isAlive()) ping(); - try { Thread.sleep(Router.LIVELINESS_DELAY); } catch (InterruptedException ie) {} - } while (_router.isAlive()); - _pingFile.delete(); + else + _pingFile.delete(); } private void ping() { From 3d759d76cf53d73026caae1b52c4f5de79fb6c42 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 20:39:22 +0000 Subject: [PATCH 17/67] javadoc, start notifier only after success --- .../src/net/i2p/client/I2PSessionImpl.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index a197a8209..9943d16ec 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -283,8 +283,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa setOpening(true); _closed = false; _availabilityNotifier.stopNotifying(); - Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true); - notifier.start(); if ( (_options != null) && (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(_options.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT))) ) { @@ -315,6 +313,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa InputStream in = _socket.getInputStream(); _reader = new I2CPMessageReader(in, this); } + Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true); + notifier.start(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading"); _reader.startReading(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate"); @@ -454,6 +454,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } } + /** + * This notifies the client of payload messages. + * Needs work. + */ protected class AvailabilityNotifier implements Runnable { private List _pendingIds; private List _pendingSizes; @@ -516,8 +520,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } /** - * Recieve notification of some I2CP message and handle it if possible - * + * The I2CPMessageEventListener callback. + * Recieve notification of some I2CP message and handle it if possible. + * @param reader unused */ public void messageReceived(I2CPMessageReader reader, I2CPMessage message) { I2CPMessageHandler handler = _handlerMap.getHandler(message.getType()); @@ -534,7 +539,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } /** - * Recieve notifiation of an error reading the I2CP stream + * The I2CPMessageEventListener callback. + * Recieve notifiation of an error reading the I2CP stream. + * @param reader unused * @param error non-null */ public void readError(I2CPMessageReader reader, Exception error) { @@ -691,7 +698,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } /** - * Recieve notification that the I2CP connection was disconnected + * The I2CPMessageEventListener callback. + * Recieve notification that the I2CP connection was disconnected. + * @param reader unused */ public void disconnected(I2CPMessageReader reader) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnected", new Exception("Disconnected")); From 04af2550453814945437e60ea3b4662ddb858519 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 20:39:40 +0000 Subject: [PATCH 18/67] SimpleSession doesnt need a notifier thread --- core/java/src/net/i2p/client/I2PSimpleSession.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index 06f31443a..dac914d64 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -52,7 +52,6 @@ class I2PSimpleSession extends I2PSessionImpl2 { _handlerMap = new SimpleMessageHandlerMap(context); _closed = true; _closing = false; - _availabilityNotifier = new AvailabilityNotifier(); if (options == null) options = System.getProperties(); loadConfig(options); @@ -68,9 +67,6 @@ class I2PSimpleSession extends I2PSessionImpl2 { @Override public void connect() throws I2PSessionException { _closed = false; - _availabilityNotifier.stopNotifying(); - Thread notifier = new I2PAppThread(_availabilityNotifier, "Simple Notifier", true); - notifier.start(); try { // If we are in the router JVM, connect using the interal queue @@ -93,6 +89,7 @@ class I2PSimpleSession extends I2PSessionImpl2 { InputStream in = _socket.getInputStream(); _reader = new I2CPMessageReader(in, this); } + // we do not receive payload messages, so we do not need an AvailabilityNotifier _reader.startReading(); } catch (UnknownHostException uhe) { From b42967848eef63d6d723197103abe050db4d82b2 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 20:39:51 +0000 Subject: [PATCH 19/67] javadoc --- core/java/src/net/i2p/util/SimpleScheduler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java index 2b1ddf44f..f764debe9 100644 --- a/core/java/src/net/i2p/util/SimpleScheduler.java +++ b/core/java/src/net/i2p/util/SimpleScheduler.java @@ -69,6 +69,13 @@ public class SimpleScheduler { re.schedule(); } + /** + * Queue up the given event to be fired after timeoutMs and every + * timeoutMs thereafter. The TimedEvent must not do its own rescheduling. + * As all Exceptions are caught in run(), these will not prevent + * subsequent executions (unlike SimpleTimer, where the TimedEvent does + * its own rescheduling). + */ public void addPeriodicEvent(SimpleTimer.TimedEvent event, long timeoutMs) { addPeriodicEvent(event, timeoutMs, timeoutMs); } From 0b2bc726df4adf58fef4083813a3e3b587059684 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 21:18:37 +0000 Subject: [PATCH 20/67] fix SimpleSession NPE --- core/java/src/net/i2p/client/I2PSessionImpl.java | 4 +++- core/java/src/net/i2p/client/I2PSimpleSession.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 9943d16ec..4b65d422b 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -659,7 +659,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa propogateError("Error destroying the session", ipe); } } - _availabilityNotifier.stopNotifying(); + // SimpleSession does not initialize + if (_availabilityNotifier != null) + _availabilityNotifier.stopNotifying(); _closed = true; _closing = false; closeSocket(); diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index dac914d64..4f15c16ff 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -47,6 +47,7 @@ class I2PSimpleSession extends I2PSessionImpl2 { * @throws I2PSessionException if there is a problem */ public I2PSimpleSession(I2PAppContext context, Properties options) throws I2PSessionException { + // Warning, does not call super() _context = context; _log = context.logManager().getLog(I2PSimpleSession.class); _handlerMap = new SimpleMessageHandlerMap(context); From 86de251691132badafc9cdd4a1da3c2185066cb5 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 4 Dec 2010 22:27:24 +0000 Subject: [PATCH 21/67] fix QueuedI2CPMessageReader not stopping; javadocs --- .../src/net/i2p/data/i2cp/I2CPMessageReader.java | 9 ++++++--- .../net/i2p/internal/QueuedI2CPMessageReader.java | 9 ++++++++- .../src/net/i2p/data/i2np/I2NPMessageReader.java | 15 ++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java index 4ac902a48..39633881e 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java @@ -68,7 +68,7 @@ public class I2CPMessageReader { /** * Have the already started reader pause its reading indefinitely - * + * @deprecated unused */ public void pauseReading() { _reader.pauseRunner(); @@ -76,7 +76,7 @@ public class I2CPMessageReader { /** * Resume reading after a pause - * + * @deprecated unused */ public void resumeReading() { _reader.resumeRunner(); @@ -131,10 +131,12 @@ public class I2CPMessageReader { _stayAlive = true; } + /** deprecated unused */ public void pauseRunner() { _doRun = false; } + /** deprecated unused */ public void resumeRunner() { _doRun = true; } @@ -181,7 +183,8 @@ public class I2CPMessageReader { cancelRunner(); } } - if (!_doRun) { + // ??? unused + if (_stayAlive && !_doRun) { // pause .5 secs when we're paused try { Thread.sleep(500); diff --git a/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java b/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java index da128ceaa..d713b678d 100644 --- a/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java +++ b/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java @@ -26,6 +26,12 @@ public class QueuedI2CPMessageReader extends I2CPMessageReader { super(); } + @Override + public void cancelRunner() { + super.cancelRunner(); + _readerThread.interrupt(); + } + @Override public void run() { while (_stayAlive) { @@ -40,7 +46,8 @@ public class QueuedI2CPMessageReader extends I2CPMessageReader { _listener.messageReceived(QueuedI2CPMessageReader.this, msg); } catch (InterruptedException ie) {} } - if (!_doRun) { + // ??? unused + if (_stayAlive && !_doRun) { // pause .5 secs when we're paused try { Thread.sleep(500); diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java index 68dabbcc7..74b93b34f 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java @@ -56,16 +56,19 @@ public class I2NPMessageReader { * */ public void startReading() { _readerThread.start(); } + /** * Have the already started reader pause its reading indefinitely - * + * @deprecated unused */ public void pauseReading() { _reader.pauseRunner(); } + /** * Resume reading after a pause - * + * @deprecated unused */ public void resumeReading() { _reader.resumeRunner(); } + /** * Cancel reading. * @@ -106,8 +109,13 @@ public class I2NPMessageReader { _stayAlive = true; _handler = new I2NPMessageHandler(_context); } + + /** deprecated unused */ public void pauseRunner() { _doRun = false; } + + /** deprecated unused */ public void resumeRunner() { _doRun = true; } + public void cancelRunner() { _doRun = false; _stayAlive = false; @@ -153,7 +161,8 @@ public class I2NPMessageReader { cancelRunner(); } } - if (!_doRun) { + // ??? unused + if (_stayAlive && !_doRun) { // pause .5 secs when we're paused try { Thread.sleep(500); } catch (InterruptedException ie) {} } From 4a9f7b740c74a4bf20e9753d67a712b4e249de86 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 5 Dec 2010 19:04:33 +0000 Subject: [PATCH 22/67] * Console: - Add SSL support - To enable, change clients.config. Examples: ## Change to SSL only - just add a '-s' clientApp.0.args=-s 7657 ::1,127.0.0.1 ./webapps/ ## Use both non-SSL and SSL - add '-s port interface' clientApp.0.args=7657 ::1,127.0.0.1 -s 7667 ::1,127.0.0.1 ./webapps/ ## ...and change URLLauncher args further down for the browser to open https:// at startup if you like. --- .../i2p/router/web/RouterConsoleRunner.java | 229 ++++++++++++++++-- core/java/src/net/i2p/util/ShellCommand.java | 73 +++++- installer/resources/clients.config | 15 ++ 3 files changed, 289 insertions(+), 28 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 68533f7ef..7f415894d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -10,31 +10,46 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.apps.systray.SysTray; +import net.i2p.data.Base32; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; +import net.i2p.util.ShellCommand; import org.mortbay.http.DigestAuthenticator; import org.mortbay.http.HashUserRealm; import org.mortbay.http.SecurityConstraint; +import org.mortbay.http.SslListener; import org.mortbay.http.handler.SecurityHandler; import org.mortbay.jetty.Server; import org.mortbay.jetty.servlet.WebApplicationContext; import org.mortbay.jetty.servlet.WebApplicationHandler; +import org.mortbay.util.InetAddrPort; public class RouterConsoleRunner { private Server _server; - private String _listenPort = "7657"; - private String _listenHost = "127.0.0.1"; - private String _webAppsDir = "./webapps/"; + private String _listenPort; + private String _listenHost; + private String _sslListenPort; + private String _sslListenHost; + private String _webAppsDir; private static final String PROP_WEBAPP_CONFIG_FILENAME = "router.webappsConfigFile"; private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config"; private static final DigestAuthenticator authenticator = new DigestAuthenticator(); public static final String ROUTERCONSOLE = "routerconsole"; public static final String PREFIX = "webapps."; public static final String ENABLED = ".startOnLoad"; + private static final String PROP_KEYSTORE_PASSWORD = "routerconsole.keystorePassword"; + private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit"; + private static final String PROP_KEY_PASSWORD = "routerconsole.keyPassword"; + private static final String DEFAULT_LISTEN_PORT = "7657"; + private static final String DEFAULT_LISTEN_HOST = "127.0.0.1"; + private static final String DEFAULT_WEBAPPS_DIR = "./webapps/"; + private static final String USAGE = "Bad RouterConsoleRunner arguments, check clientApp.0.args in your clients.config file! " + + "Usage: [[port host[,host]] [-s sslPort [host[,host]]] [webAppsDir]]"; static { System.setProperty("org.mortbay.http.Version.paranoid", "true"); @@ -42,6 +57,27 @@ public class RouterConsoleRunner { } /** + * <pre> + * non-SSL: + * RouterConsoleRunner + * RouterConsoleRunner 7657 + * RouterConsoleRunner 7657 127.0.0.1 + * RouterConsoleRunner 7657 127.0.0.1,::1 + * RouterConsoleRunner 7657 127.0.0.1,::1 ./webapps/ + * + * SSL: + * RouterConsoleRunner -s 7657 + * RouterConsoleRunner -s 7657 127.0.0.1 + * RouterConsoleRunner -s 7657 127.0.0.1,::1 + * RouterConsoleRunner -s 7657 127.0.0.1,::1 ./webapps/ + * + * If using both, non-SSL must be first: + * RouterConsoleRunner 7657 127.0.0.1 -s 7667 + * RouterConsoleRunner 7657 127.0.0.1 -s 7667 127.0.0.1 + * RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1 + * RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1 ./webapps/ + * </pre> + * * @param args second arg may be a comma-separated list of bind addresses, * for example ::1,127.0.0.1 * On XP, the other order (127.0.0.1,::1) fails the IPV6 bind, @@ -50,10 +86,40 @@ public class RouterConsoleRunner { * So the wise choice is ::1,127.0.0.1 */ public RouterConsoleRunner(String args[]) { - if (args.length == 3) { - _listenPort = args[0].trim(); - _listenHost = args[1].trim(); - _webAppsDir = args[2].trim(); + if (args.length == 0) { + // _listenHost and _webAppsDir are defaulted above + _listenPort = DEFAULT_LISTEN_PORT; + } else { + boolean ssl = false; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-s")) + ssl = true; + else if ((!ssl) && _listenPort == null) + _listenPort = args[i]; + else if ((!ssl) && _listenHost == null) + _listenHost = args[i]; + else if (ssl && _sslListenPort == null) + _sslListenPort = args[i]; + else if (ssl && _sslListenHost == null) + _sslListenHost = args[i]; + else if (_webAppsDir == null) + _webAppsDir = args[i]; + else { + System.err.println(USAGE); + throw new IllegalArgumentException(USAGE); + } + } + } + if (_listenHost == null) + _listenHost = DEFAULT_LISTEN_HOST; + if (_sslListenHost == null) + _sslListenHost = _listenHost; + if (_webAppsDir == null) + _webAppsDir = DEFAULT_WEBAPPS_DIR; + // _listenPort and _sslListenPort are not defaulted, if one or the other is null, do not enable + if (_listenPort == null && _sslListenPort == null) { + System.err.println(USAGE); + throw new IllegalArgumentException(USAGE); } } @@ -96,22 +162,63 @@ public class RouterConsoleRunner { List<String> notStarted = new ArrayList(); WebApplicationHandler baseHandler = null; try { - StringTokenizer tok = new StringTokenizer(_listenHost, " ,"); int boundAddresses = 0; - while (tok.hasMoreTokens()) { - String host = tok.nextToken().trim(); - try { - if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5 - _server.addListener('[' + host + "]:" + _listenPort); - else - _server.addListener(host + ':' + _listenPort); - boundAddresses++; - } catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below - System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe); + + // add standard listeners + if (_listenPort != null) { + StringTokenizer tok = new StringTokenizer(_listenHost, " ,"); + while (tok.hasMoreTokens()) { + String host = tok.nextToken().trim(); + try { + if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5 + _server.addListener('[' + host + "]:" + _listenPort); + else + _server.addListener(host + ':' + _listenPort); + boundAddresses++; + } catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below + System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe); + } } } + + // add SSL listeners + int sslPort = 0; + if (_sslListenPort != null) { + try { + sslPort = Integer.parseInt(_sslListenPort); + } catch (NumberFormatException nfe) {} + if (sslPort <= 0) + System.err.println("Bad routerconsole SSL port " + _sslListenPort); + } + if (sslPort > 0) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + File keyStore = new File(ctx.getConfigDir(), "keystore/console.ks"); + if (verifyKeyStore(keyStore)) { + StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,"); + while (tok.hasMoreTokens()) { + String host = tok.nextToken().trim(); + // doing it this way means we don't have to escape an IPv6 host with [] + InetAddrPort iap = new InetAddrPort(host, sslPort); + try { + SslListener ssll = new SslListener(iap); + // the keystore path and password + ssll.setKeystore(keyStore.getAbsolutePath()); + ssll.setPassword(ctx.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD)); + // the X.509 cert password (if not present, verifyKeyStore() returned false) + ssll.setKeyPassword(ctx.getProperty(PROP_KEY_PASSWORD, "thisWontWork")); + _server.addListener(ssll); + boundAddresses++; + } catch (Exception e) { // probably no exceptions at this point + System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + " for SSL: " + e); + } + } + } else { + System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath()); + } + } + if (boundAddresses <= 0) { - System.err.println("Unable to bind routerconsole to any address on port " + _listenPort); + System.err.println("Unable to bind routerconsole to any address on port " + _listenPort + (sslPort > 0 ? (" or SSL port " + sslPort) : "")); return; } _server.setRootWebApp(ROUTERCONSOLE); @@ -201,6 +308,90 @@ public class RouterConsoleRunner { } } + /** + * @return success if it exists and we have a password, or it was created successfully. + * @since 0.8.3 + */ + private static boolean verifyKeyStore(File ks) { + if (ks.exists()) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + boolean rv = ctx.getProperty(PROP_KEY_PASSWORD) != null; + if (!rv) + System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath()); + return rv; + } + File dir = ks.getParentFile(); + if (!dir.exists()) { + File sdir = new SecureDirectory(dir.getAbsolutePath()); + if (!sdir.mkdir()) + return false; + } + return createKeyStore(ks); + } + + + /** + * Call out to keytool to create a new keystore with a keypair in it. + * Trying to do this programatically is a nightmare, requiring either BouncyCastle + * libs or using proprietary Sun libs, and it's a huge mess. + * + * @return success + * @since 0.8.3 + */ + private static boolean createKeyStore(File ks) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + // make a random 48 character password (30 * 8 / 5) + byte[] rand = new byte[30]; + ctx.random().nextBytes(rand); + String keyPassword = Base32.encode(rand); + // and one for the cname + ctx.random().nextBytes(rand); + String cname = Base32.encode(rand) + ".console.i2p.net"; + + String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath(); + String[] args = new String[] { + keytool, + "-genkey", // -genkeypair preferred in newer keytools, but this works with more + "-storetype", "JKS", + "-keystore", ks.getAbsolutePath(), + "-storepass", DEFAULT_KEYSTORE_PASSWORD, + "-alias", "console", + "-dname", "CN=" + cname + ",OU=Console,O=I2P Anonymous Network,L=XX,ST=XX,C=XX", + "-validity", "3652", // 10 years + "-keyalg", "DSA", + "-keysize", "1024", + "-keypass", keyPassword}; + boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs + if (success) { + success = ks.exists(); + if (success) { + SecureFileOutputStream.setPerms(ks); + try { + RouterContext rctx = (RouterContext) ctx; + rctx.router().setConfigSetting(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + rctx.router().setConfigSetting(PROP_KEY_PASSWORD, keyPassword); + rctx.router().saveConfig(); + } catch (Exception e) {} // class cast exception + } + } + if (success) { + System.err.println("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" + + "The certificate name was generated randomly, and is not associated with your " + + "IP address, host name, router identity, or destination keys."); + } else { + System.err.println("Failed to create console SSL keystore using command line:"); + StringBuilder buf = new StringBuilder(256); + for (int i = 0; i < args.length; i++) { + buf.append('"').append(args[i]).append("\" "); + } + System.err.println(buf.toString()); + System.err.println("This is for the Sun/Oracle keytool, others may be incompatible.\n" + + "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD + + " to " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath()); + } + return success; + } + static void initialize(WebApplicationContext context) { String password = getPassword(); if (password != null) { diff --git a/core/java/src/net/i2p/util/ShellCommand.java b/core/java/src/net/i2p/util/ShellCommand.java index d6fce0021..12b668f67 100644 --- a/core/java/src/net/i2p/util/ShellCommand.java +++ b/core/java/src/net/i2p/util/ShellCommand.java @@ -51,17 +51,18 @@ public class ShellCommand { */ private class CommandThread extends Thread { - final Object caller; - boolean consumeOutput; - String shellCommand; + private final Object caller; + private final boolean consumeOutput; + private final Object shellCommand; - CommandThread(Object caller, String shellCommand, boolean consumeOutput) { + /** + * @param shellCommand either a String or a String[] (since 0.8.3) + */ + CommandThread(Object caller, Object shellCommand, boolean consumeOutput) { super("CommandThread"); this.caller = caller; this.shellCommand = shellCommand; this.consumeOutput = consumeOutput; - _commandSuccessful = false; - _commandCompleted = false; } @Override @@ -200,6 +201,9 @@ public class ShellCommand { * {@link #getErrorStream()}, respectively. Input can be passed to the * <code>STDIN</code> of the shell process via {@link #getInputStream()}. * + * Warning, no good way to quote or escape spaces in arguments with this method. + * @deprecated unused + * * @param shellCommand The command for the shell to execute. */ public void execute(String shellCommand) { @@ -215,6 +219,9 @@ public class ShellCommand { * Input can be passed to the <code>STDIN</code> of the shell process via * {@link #getInputStream()}. * + * Warning, no good way to quote or escape spaces in arguments with this method. + * @deprecated unused + * * @param shellCommand The command for the shell to execute. * @return <code>true</code> if the spawned shell process * returns an exit status of 0 (indicating success), @@ -237,6 +244,9 @@ public class ShellCommand { * {@link #getErrorStream()}, respectively. Input can be passed to the * <code>STDIN</code> of the shell process via {@link #getInputStream()}. * + * Warning, no good way to quote or escape spaces in arguments with this method. + * @deprecated unused + * * @param shellCommand The command for the shell to execute. * @param seconds The method will return <code>true</code> if this * number of seconds elapses without the process @@ -276,6 +286,9 @@ public class ShellCommand { * without waiting for an exit status. Any output produced by the executed * command will not be displayed. * + * Warning, no good way to quote or escape spaces in arguments with this method. + * @deprecated unused + * * @param shellCommand The command for the shell to execute. * @throws IOException */ @@ -288,6 +301,8 @@ public class ShellCommand { * all of the command's resulting shell processes have completed. Any output * produced by the executed command will not be displayed. * + * Warning, no good way to quote or escape spaces in arguments with this method. + * * @param shellCommand The command for the shell to execute. * @return <code>true</code> if the spawned shell process * returns an exit status of 0 (indicating success), @@ -307,7 +322,12 @@ public class ShellCommand { * specified number of seconds has elapsed first. Any output produced by the * executed command will not be displayed. * - * @param shellCommand The command for the shell to execute. + * Warning, no good way to quote or escape spaces in arguments when shellCommand is a String. + * Use a String array for best results, especially on Windows. + * + * @param shellCommand The command for the shell to execute, as a String. + * You can't quote arguments successfully. + * See Runtime.exec(String) for more info. * @param seconds The method will return <code>true</code> if this * number of seconds elapses without the process * returning an exit status. A value of <code>0</code> @@ -317,7 +337,33 @@ public class ShellCommand { * else <code>false</code>. */ public synchronized boolean executeSilentAndWaitTimed(String shellCommand, int seconds) { + return executeSAWT(shellCommand, seconds); + } + /** + * Passes a command to the shell for execution. This method blocks until + * all of the command's resulting shell processes have completed unless a + * specified number of seconds has elapsed first. Any output produced by the + * executed command will not be displayed. + * + * @param commandArray The command for the shell to execute, + * as a String[]. + * See Runtime.exec(String[]) for more info. + * @param seconds The method will return <code>true</code> if this + * number of seconds elapses without the process + * returning an exit status. A value of <code>0</code> + * here disables waiting. + * @return <code>true</code> if the spawned shell process + * returns an exit status of 0 (indicating success), + * else <code>false</code>. + * @since 0.8.3 + */ + public synchronized boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) { + return executeSAWT(commandArray, seconds); + } + + /** @since 0.8.3 */ + private boolean executeSAWT(Object shellCommand, int seconds) { _commandThread = new CommandThread(this, shellCommand, CONSUME_OUTPUT); _commandThread.start(); try { @@ -364,7 +410,10 @@ public class ShellCommand { return; } - private boolean execute(String shellCommand, boolean consumeOutput, boolean waitForExitStatus) { + /** + * @param shellCommand either a String or a String[] (since 0.8.3) - quick hack + */ + private boolean execute(Object shellCommand, boolean consumeOutput, boolean waitForExitStatus) { StreamConsumer processStderrConsumer; StreamConsumer processStdoutConsumer; @@ -374,7 +423,13 @@ public class ShellCommand { StreamReader processStdoutReader; try { - _process = Runtime.getRuntime().exec(shellCommand, null); + // easy way so we don't have to copy this whole method + if (shellCommand instanceof String) + _process = Runtime.getRuntime().exec((String)shellCommand); + else if (shellCommand instanceof String[]) + _process = Runtime.getRuntime().exec((String[])shellCommand); + else + throw new ClassCastException("shell command must be a String or a String[]"); if (consumeOutput) { processStderrConsumer = new StreamConsumer(_process.getErrorStream()); processStderrConsumer.start(); diff --git a/installer/resources/clients.config b/installer/resources/clients.config index f82aec526..08c6c62ba 100644 --- a/installer/resources/clients.config +++ b/installer/resources/clients.config @@ -6,6 +6,21 @@ # # fire up the web console +## There are several choices, here are some examples: +## non-SSL, bind to local IPv4 only +#clientApp.0.args=7657 127.0.0.1 ./webapps/ +## non-SSL, bind to local IPv6 only +#clientApp.0.args=7657 ::1 ./webapps/ +## non-SSL, bind to all IPv4 addresses +#clientApp.0.args=7657 0.0.0.0 ./webapps/ +## non-SSL, bind to all IPv6 addresses +#clientApp.0.args=7657 :: ./webapps/ +## For SSL only, change clientApp.4.args below to https:// +## SSL only +#clientApp.0.args=-s 7657 ::1,127.0.0.1 ./webapps/ +## non-SSL and SSL +#clientApp.0.args=7657 ::1,127.0.0.1 -s 7667 ::1,127.0.0.1 ./webapps/ +## non-SSL only, both IPv6 and IPv4 local interfaces clientApp.0.args=7657 ::1,127.0.0.1 ./webapps/ clientApp.0.main=net.i2p.router.web.RouterConsoleRunner clientApp.0.name=I2P Router Console From 653a68b8a5df494088d15d783e3952c28d28a65f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 21:30:29 +0000 Subject: [PATCH 23/67] Disable I2CP host/port options when in router context --- .../src/net/i2p/i2ptunnel/web/EditBean.java | 9 +++++++++ .../src/net/i2p/i2ptunnel/web/IndexBean.java | 20 ++++++++++--------- apps/i2ptunnel/jsp/editClient.jsp | 6 +++--- apps/i2ptunnel/jsp/editServer.jsp | 6 +++--- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index b60dc289e..7c753e6ab 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -270,7 +270,14 @@ public class EditBean extends IndexBean { return false; } + /** @since 0.8.3 */ + public boolean isRouterContext() { + return _context.isRouterContext(); + } + public String getI2CPHost(int tunnel) { + if (_context.isRouterContext()) + return _("internal"); TunnelController tun = getController(tunnel); if (tun != null) return tun.getI2CPHost(); @@ -279,6 +286,8 @@ public class EditBean extends IndexBean { } public String getI2CPPort(int tunnel) { + if (_context.isRouterContext()) + return _("internal"); TunnelController tun = getController(tunnel); if (tun != null) return tun.getI2CPPort(); 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 0640c2a31..5e38b2b4e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -537,11 +537,11 @@ public class IndexBean { public void setDescription(String description) { _description = (description != null ? description.trim() : null); } - /** I2CP host the router is on */ + /** I2CP host the router is on, ignored when in router context */ public void setClientHost(String host) { _i2cpHost = (host != null ? host.trim() : null); } - /** I2CP port the router is on */ + /** I2CP port the router is on, ignored when in router context */ public void setClientport(String port) { _i2cpPort = (port != null ? port.trim() : null); } @@ -929,12 +929,14 @@ public class IndexBean { config.setProperty("name", _name); if (_description != null) config.setProperty("description", _description); - if (_i2cpHost != null) - config.setProperty("i2cpHost", _i2cpHost); - if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) { - config.setProperty("i2cpPort", _i2cpPort); - } else { - config.setProperty("i2cpPort", "7654"); + if (!_context.isRouterContext()) { + if (_i2cpHost != null) + config.setProperty("i2cpHost", _i2cpHost); + if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) { + config.setProperty("i2cpPort", _i2cpPort); + } else { + config.setProperty("i2cpPort", "7654"); + } } if (_privKeyFile != null) config.setProperty("privKeyFile", _privKeyFile); @@ -1020,7 +1022,7 @@ public class IndexBean { } } - private String _(String key) { + protected String _(String key) { return Messages._(key, _context); } } diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 2b69440ac..b6475f754 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -286,19 +286,19 @@ <% } // !streamrclient %> <div id="optionsField" class="rowItem"> - <label><%=intl._("I2CP Options")%>:</label> + <label><%=intl._("Router I2CP Address")%>:</label> </div> <div id="optionsHostField" class="rowItem"> <label for="clientHost" accesskey="o"> <%=intl._("Host")%>(<span class="accessKey">o</span>): </label> - <input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" /> + <input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" <% if (editBean.isRouterContext()) { %> readonly="readonly" <% } %> /> </div> <div id="optionsPortField" class="rowItem"> <label for="clientPort" accesskey="r"> <%=intl._("Port")%>(<span class="accessKey">r</span>): </label> - <input type="text" id="port" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" /> + <input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" <% if (editBean.isRouterContext()) { %> readonly="readonly" <% } %> /> </div> <% if (!"streamrclient".equals(tunnelType)) { // streamr client sends pings so it will never be idle %> diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 773d323a2..5d3660453 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -305,19 +305,19 @@ <% } // !streamrserver %> <div id="optionsField" class="rowItem"> - <label><%=intl._("I2CP Options")%>:</label> + <label><%=intl._("Router I2CP Address")%>:</label> </div> <div id="optionsHostField" class="rowItem"> <label for="clientHost" accesskey="o"> <%=intl._("Host")%>(<span class="accessKey">o</span>): </label> - <input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" /> + <input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" <% if (editBean.isRouterContext()) { %> readonly="readonly" <% } %> /> </div> <div id="optionsPortField" class="rowItem"> <label for="clientPort" accesskey="r"> <%=intl._("Port")%>(<span class="accessKey">r</span>): </label> - <input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" /> + <input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" <% if (editBean.isRouterContext()) { %> readonly="readonly" <% } %> /> </div> <div class="subdivider"> From 384f1bd1748803629756499026793f083f19249c Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 21:32:00 +0000 Subject: [PATCH 24/67] update jetty.xml SSL comments --- installer/resources/jetty.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/installer/resources/jetty.xml b/installer/resources/jetty.xml index d82cf5580..29900cb6b 100644 --- a/installer/resources/jetty.xml +++ b/installer/resources/jetty.xml @@ -71,17 +71,29 @@ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> - <!-- Add a HTTPS SSL listener on port 8443 --> + <!-- Add a HTTPS SSL listener on port 8443 --> + <!-- --> + <!-- In the unlikely event you would want SSL support for your eepsite. --> + <!-- You would need to generate a selfsigned certificate in a keystore --> + <!-- in ~/.i2p/eepsite/keystore.ks, for example with the command line: --> + <!-- + keytool -genkey -storetype JKS -keystore ~/.i2p/eepsite/keystore.ks -storepass changeit -alias console -dname CN=xyz123.eepsite.i2p.net,OU=Eepsite,O=I2P Anonymous Network,L=XX,ST=XX,C=XX -validity 3650 -keyalg DSA -keysize 1024 -keypass myKeyPassword + --> + <!-- Change the CN and key password in the example, of course. --> + <!-- You wouldn't want to open this up to the regular internet, --> + <!-- would you?? Untested and not recommended. --> <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <!-- UNCOMMENT TO ACTIVATE <Call name="addListener"> <Arg> - <New class="org.mortbay.http.SunJsseListener"> + <New class="org.mortbay.http.SslListener"> <Set name="Port">8443</Set> <Set name="PoolName">main</Set> - <Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set> - <Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set> - <Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set> + <Set name="Keystore">./eepsite/keystore.ks</Set> + <!-- the keystore password --> + <Set name="Password">changeit</Set> + <!-- the X.509 certificate password --> + <Set name="KeyPassword">myKeyPassword</Set> <Set name="NonPersistentUserAgent">MSIE 5</Set> </New> </Arg> From 114c398056397873f8ca2e2858208129154e3111 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 21:45:32 +0000 Subject: [PATCH 25/67] shorten TrackerClient thread name --- apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index e3cc8ae9f..a1a3f4cc2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -72,8 +72,10 @@ public class TrackerClient extends I2PAppThread public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator) { + super(); // Set unique name. - super("TrackerClient-" + urlencode(coordinator.getID())); + String id = urlencode(coordinator.getID()); + setName("TrackerClient " + id.substring(id.length() - 12)); _util = util; this.meta = meta; this.coordinator = coordinator; From 9a993c00e44a184a021122c17b70c8e7ebf6ca11 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 22:04:06 +0000 Subject: [PATCH 26/67] * graphs.jsp: - Show selected refresh delay - Translate refresh delays - Fix setting delay to 'Never' --- .../src/net/i2p/router/web/GraphHelper.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java index b3ce2fa83..1b0e043e3 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java @@ -59,7 +59,13 @@ public class GraphHelper extends FormHandler { try { _width = Math.min(Integer.parseInt(str), MAX_X); } catch (NumberFormatException nfe) {} } public void setRefreshDelay(String str) { - try { _refreshDelaySeconds = Math.max(Integer.parseInt(str), MIN_REFRESH); } catch (NumberFormatException nfe) {} + try { + int rds = Integer.parseInt(str); + if (rds > 0) + _refreshDelaySeconds = Math.max(rds, MIN_REFRESH); + else + _refreshDelaySeconds = -1; + } catch (NumberFormatException nfe) {} } public String getImages() { @@ -83,7 +89,7 @@ public class GraphHelper extends FormHandler { + "&periodCount=" + (3 * _periodCount ) + "&width=" + (3 * _width) + "&height=" + (3 * _height) - + "\" / target=\"_blank\">"); + + "\" target=\"_blank\">"); String title = _("Combined bandwidth graph"); _out.write("<img class=\"statimage\" width=\"" + (_width + 83) + "\" height=\"" + (_height + 92) @@ -129,6 +135,8 @@ public class GraphHelper extends FormHandler { return ""; } + private static final int[] times = { 60, 2*60, 5*60, 10*60, 30*60, 60*60, -1 }; + public String getForm() { String prev = System.getProperty("net.i2p.router.web.GraphHelper.nonce"); if (prev != null) System.setProperty("net.i2p.router.web.GraphHelper.noncePrev", prev); @@ -145,8 +153,22 @@ public class GraphHelper extends FormHandler { _out.write(_("Image sizes") + ": " + _("width") + ": <input size=\"4\" type=\"text\" name=\"width\" value=\"" + _width + "\"> " + _("pixels") + ", " + _("height") + ": <input size=\"4\" type=\"text\" name=\"height\" value=\"" + _height + "\"> " + _("pixels") + "<br>\n"); - _out.write(_("Refresh delay") + ": <select name=\"refreshDelay\"><option value=\"60\">1 " + _("minute") + "</option><option value=\"120\">2 " + _("minutes") + "</option><option value=\"300\">5 " + _("minutes") + "</option><option value=\"600\">10 " + _("minutes") + "</option><option value=\"1800\">30 " + _("minutes") + "</option><option value=\"3600\">1 " + _("hour") + "</option><option value=\"-1\">" + _("Never") + "</option></select><br>\n"); - _out.write("<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>"); + _out.write(_("Refresh delay") + ": <select name=\"refreshDelay\">"); + for (int i = 0; i < times.length; i++) { + _out.write("<option value=\""); + _out.write(Integer.toString(times[i])); + _out.write("\""); + if (times[i] == _refreshDelaySeconds) + _out.write(" selected=\"true\""); + _out.write(">"); + if (times[i] > 0) + _out.write(DataHelper.formatDuration2(times[i] * 1000)); + else + _out.write(_("Never")); + _out.write("</option>\n"); + } + _out.write("</select><br>\n" + + "<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>"); } catch (IOException ioe) { ioe.printStackTrace(); } From 6d41aadc24acbfe101de6954fa9f85b7bb1c09a6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 22:06:30 +0000 Subject: [PATCH 27/67] * I2CP: - New option i2cp.disableInterface to turn off external I2CP port (7654) and allow internal clients only - Disallow internal connect when shut down --- .../net/i2p/router/client/ClientManager.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index 948f895bb..2a3a4c6ed 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -43,12 +43,16 @@ import net.i2p.util.Log; * * @author jrandom */ -public class ClientManager { - private Log _log; +class ClientManager { + private final Log _log; private ClientListenerRunner _listener; private final HashMap<Destination, ClientConnectionRunner> _runners; // Destination --> ClientConnectionRunner private final Set<ClientConnectionRunner> _pendingRunners; // ClientConnectionRunner for clients w/out a Dest yet - private RouterContext _ctx; + private final RouterContext _ctx; + private boolean _isStarted; + + /** Disable external interface, allow internal clients only @since 0.8.3 */ + private static final String PROP_DISABLE_EXTERNAL = "i2cp.disableInterface"; /** ms to wait before rechecking for inbound messages to deliver to clients */ private final static int INBOUND_POLL_INTERVAL = 300; @@ -67,11 +71,12 @@ public class ClientManager { /** Todo: Start a 3rd listener for IPV6? */ private void startListeners(int port) { - _listener = new ClientListenerRunner(_ctx, this, port); - Thread t = new I2PThread(_listener); - t.setName("ClientListener:" + port); - t.setDaemon(true); - t.start(); + if (!_ctx.getBooleanProperty(PROP_DISABLE_EXTERNAL)) { + _listener = new ClientListenerRunner(_ctx, this, port); + Thread t = new I2PThread(_listener, "ClientListener:" + port, true); + t.start(); + } + _isStarted = true; } public void restart() { @@ -93,8 +98,10 @@ public class ClientManager { } public void shutdown() { + _isStarted = false; _log.info("Shutting down the ClientManager"); - _listener.stopListening(); + if (_listener != null) + _listener.stopListening(); Set<ClientConnectionRunner> runners = new HashSet(); synchronized (_runners) { for (Iterator<ClientConnectionRunner> iter = _runners.values().iterator(); iter.hasNext();) { @@ -117,10 +124,12 @@ public class ClientManager { /** * The InternalClientManager interface. * Connects to the router, receiving a message queue to talk to the router with. - * Might throw I2PSessionException if the router isn't ready, someday. + * @throws I2PSessionException if the router isn't ready * @since 0.8.3 */ - public I2CPMessageQueue internalConnect() { + public I2CPMessageQueue internalConnect() throws I2PSessionException { + if (!_isStarted) + throw new I2PSessionException("Router client manager is shut down"); // for now we make these unlimited size LinkedBlockingQueue<I2CPMessage> in = new LinkedBlockingQueue(); LinkedBlockingQueue<I2CPMessage> out = new LinkedBlockingQueue(); @@ -131,7 +140,9 @@ public class ClientManager { return hisQueue; } - public boolean isAlive() { return _listener.isListening(); } + public boolean isAlive() { + return _isStarted && (_listener == null || _listener.isListening()); + } public void registerConnection(ClientConnectionRunner runner) { synchronized (_pendingRunners) { From a5354f64a5cd38101903348b2ca58e26bf78c534 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 22:07:26 +0000 Subject: [PATCH 28/67] more classes pkg private --- .../java/src/net/i2p/router/client/ClientConnectionRunner.java | 2 +- router/java/src/net/i2p/router/client/ClientListenerRunner.java | 2 +- .../src/net/i2p/router/client/QueuedClientConnectionRunner.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index 5807aa098..0b207da53 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -50,7 +50,7 @@ import net.i2p.util.SimpleTimer; * * @author jrandom */ -public class ClientConnectionRunner { +class ClientConnectionRunner { private Log _log; protected final RouterContext _context; private ClientManager _manager; diff --git a/router/java/src/net/i2p/router/client/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java index 7a7d448ea..4e0a91aee 100644 --- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java @@ -24,7 +24,7 @@ import net.i2p.util.Log; * * @author jrandom */ -public class ClientListenerRunner implements Runnable { +class ClientListenerRunner implements Runnable { protected Log _log; protected RouterContext _context; protected ClientManager _manager; diff --git a/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java index ea2cc3d30..4eec8e531 100644 --- a/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java @@ -16,7 +16,7 @@ import net.i2p.util.Log; * @author zzz * @since 0.8.3 */ -public class QueuedClientConnectionRunner extends ClientConnectionRunner { +class QueuedClientConnectionRunner extends ClientConnectionRunner { private final I2CPMessageQueue queue; /** From f194f78953731ca932bceedae7191e50b9823d7b Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 12 Dec 2010 22:09:04 +0000 Subject: [PATCH 29/67] use base 64 for temp dir name --- core/java/src/net/i2p/I2PAppContext.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 458aee31e..e6df37e6f 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -3,6 +3,7 @@ package net.i2p; import java.io.File; import java.util.HashSet; import java.util.Properties; +import java.util.Random; import java.util.Set; import net.i2p.client.naming.NamingService; @@ -21,6 +22,7 @@ import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TransientSessionKeyManager; +import net.i2p.data.Base64; import net.i2p.data.RoutingKeyGenerator; import net.i2p.internal.InternalClientManager; import net.i2p.stat.StatManager; @@ -364,10 +366,12 @@ public class I2PAppContext { if (_tmpDir == null) { String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir")); // our random() probably isn't warmed up yet - String f = "i2p-" + Math.abs((new java.util.Random()).nextInt()) + ".tmp"; + byte[] rand = new byte[6]; + (new Random()).nextBytes(rand); + String f = "i2p-" + Base64.encode(rand) + ".tmp"; _tmpDir = new SecureDirectory(d, f); if (_tmpDir.exists()) { - // good or bad ? + // good or bad ? loop and try again? } else if (_tmpDir.mkdir()) { _tmpDir.deleteOnExit(); } else { From 2880d61c1b7f15f7db114f8436ce489be255fb6a Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 13 Dec 2010 14:01:46 +0000 Subject: [PATCH 30/67] * Pack200: Find and use either Oracle or Apache library at runtime; neither required at compile time. --- .../i2p/router/web/ConfigUpdateHandler.java | 7 +- build.xml | 6 +- core/java/src/net/i2p/util/FileUtil.java | 68 +++++++++++++++---- 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 72acbb540..261fb4998 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -66,7 +66,12 @@ public class ConfigUpdateHandler extends FormHandler { Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader()); foo = PACK200_URLS; } catch (ClassNotFoundException cnfe) { - foo = NO_PACK200_URLS; + try { + Class.forName("org.apache.harmony.unpack200.Archive", false, ClassLoader.getSystemClassLoader()); + foo = PACK200_URLS; + } catch (ClassNotFoundException cnfe2) { + foo = NO_PACK200_URLS; + } } DEFAULT_UPDATE_URL = foo; } diff --git a/build.xml b/build.xml index 8a6bf33d2..7dfd63886 100644 --- a/build.xml +++ b/build.xml @@ -6,10 +6,8 @@ <!-- <property name="javac.compilerargs" value="-warn:-unchecked,raw,unused,serial" /> --> - <!-- Add Apache Harmony's Pack200 library if you don't have java.util.jar.Pack200 - See core/java/src/net/i2p/util/FileUtil.java for code changes required - to use this library instead of Sun's version. - Or to comment it all out if you don't have either. + <!-- Additional classpath. No longer required; we find pack200 classes at runtime. + See core/java/src/net/i2p/util/FileUtil.java for more info. --> <!-- <property name="javac.classpath" value="/PATH/TO/pack200.jar" /> diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index b56f197ab..27ad846f3 100644 --- a/core/java/src/net/i2p/util/FileUtil.java +++ b/core/java/src/net/i2p/util/FileUtil.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -16,13 +18,11 @@ import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -// Pack200 import -// you must also uncomment the correct line in unpack() below -// For gcj, gij, etc., comment both out +// Pack200 now loaded dynamically in unpack() below // // For Sun, OpenJDK, IcedTea, etc, use this -import java.util.jar.Pack200; - +//import java.util.jar.Pack200; +// // For Apache Harmony or if you put its pack200.jar in your library directory use this //import org.apache.harmony.unpack200.Archive; @@ -231,7 +231,6 @@ public class FileUtil { } /** - * This won't work right if one of the two options in unpack() is commented out. * @since 0.8.1 */ private static boolean isPack200Supported() { @@ -240,28 +239,70 @@ public class FileUtil { return true; } catch (Exception e) {} try { - Class.forName("org.apache.harmony.pack200.Archive", false, ClassLoader.getSystemClassLoader()); + Class.forName("org.apache.harmony.unpack200.Archive", false, ClassLoader.getSystemClassLoader()); return true; } catch (Exception e) {} return false; } + private static boolean _failedOracle; + private static boolean _failedApache; + /** + * Unpack using either Oracle or Apache's unpack200 library, + * with the classes discovered at runtime so neither is required at compile time. + * * Caller must close streams + * @throws IOException on unpack error or if neither library is available. + * Will not throw ClassNotFoundException. + * @throws org.apache.harmony.pack200.Pack200Exception which is not an IOException * @since 0.8.1 */ private static void unpack(InputStream in, JarOutputStream out) throws Exception { // For Sun, OpenJDK, IcedTea, etc, use this - Pack200.newUnpacker().unpack(in, out); + //Pack200.newUnpacker().unpack(in, out); + if (!_failedOracle) { + try { + Class p200 = Class.forName("java.util.jar.Pack200", true, ClassLoader.getSystemClassLoader()); + Method newUnpacker = p200.getMethod("newUnpacker", (Class[]) null); + Object unpacker = newUnpacker.invoke(null,(Object[]) null); + Method unpack = unpacker.getClass().getMethod("unpack", new Class[] {InputStream.class, JarOutputStream.class}); + // throws IOException + unpack.invoke(unpacker, new Object[] {in, out}); + return; + } catch (ClassNotFoundException e) { + _failedOracle = true; + //e.printStackTrace(); + } catch (NoSuchMethodException e) { + _failedOracle = true; + //e.printStackTrace(); + } + } // ------------------ // For Apache Harmony or if you put its pack200.jar in your library directory use this //(new Archive(in, out)).unpack(); - + if (!_failedApache) { + try { + Class p200 = Class.forName("org.apache.harmony.unpack200.Archive", true, ClassLoader.getSystemClassLoader()); + Constructor newUnpacker = p200.getConstructor(new Class[] {InputStream.class, JarOutputStream.class}); + Object unpacker = newUnpacker.newInstance(new Object[] {in, out}); + Method unpack = unpacker.getClass().getMethod("unpack", (Class[]) null); + // throws IOException or Pack200Exception + unpack.invoke(unpacker, (Object[]) null); + return; + } catch (ClassNotFoundException e) { + _failedApache = true; + //e.printStackTrace(); + } catch (NoSuchMethodException e) { + _failedApache = true; + //e.printStackTrace(); + } + } // ------------------ // For gcj, gij, etc., use this - //throw new IOException("Pack200 not supported"); + throw new IOException("Unpack200 not supported"); } /** @@ -378,12 +419,13 @@ public class FileUtil { } /** - * Usage: FileUtil (delete path | copy source dest) + * Usage: FileUtil (delete path | copy source dest | unzip path.zip) * */ public static void main(String args[]) { if ( (args == null) || (args.length < 2) ) { - testRmdir(); + System.err.println("Usage: delete path | copy source dest | unzip path.zip"); + //testRmdir(); } else if ("delete".equals(args[0])) { boolean deleted = FileUtil.rmdir(args[1], false); if (!deleted) @@ -407,6 +449,7 @@ public class FileUtil { } } + /***** private static void testRmdir() { File t = new File("rmdirTest/test/subdir/blah"); boolean created = t.mkdirs(); @@ -417,4 +460,5 @@ public class FileUtil { else System.out.println("PASS: rmdirTest deleted"); } + *****/ } From 59af763dcdd80cdf82ab5dd2a64bed583bbf6fed Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 13 Dec 2010 15:28:06 +0000 Subject: [PATCH 31/67] do it the easy way --- .../i2p/router/web/ConfigUpdateHandler.java | 18 +++++------------- core/java/src/net/i2p/util/FileUtil.java | 3 ++- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 261fb4998..8a525180b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -3,6 +3,7 @@ package net.i2p.router.web; import net.i2p.I2PAppContext; import net.i2p.crypto.TrustedUpdate; import net.i2p.data.DataHelper; +import net.i2p.util.FileUtil; /** * @@ -61,19 +62,10 @@ public class ConfigUpdateHandler extends FormHandler { public static final String DEFAULT_UPDATE_URL; static { - String foo; - try { - Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader()); - foo = PACK200_URLS; - } catch (ClassNotFoundException cnfe) { - try { - Class.forName("org.apache.harmony.unpack200.Archive", false, ClassLoader.getSystemClassLoader()); - foo = PACK200_URLS; - } catch (ClassNotFoundException cnfe2) { - foo = NO_PACK200_URLS; - } - } - DEFAULT_UPDATE_URL = foo; + if (FileUtil.isPack200Supported()) + DEFAULT_UPDATE_URL = PACK200_URLS; + else + DEFAULT_UPDATE_URL = NO_PACK200_URLS; } public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys"; diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index 27ad846f3..b5bb48a43 100644 --- a/core/java/src/net/i2p/util/FileUtil.java +++ b/core/java/src/net/i2p/util/FileUtil.java @@ -231,9 +231,10 @@ public class FileUtil { } /** + * Public since 0.8.3 * @since 0.8.1 */ - private static boolean isPack200Supported() { + public static boolean isPack200Supported() { try { Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader()); return true; From 8efefeeb5bcb89fd90812269921479c5bfce641c Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 14:27:35 +0000 Subject: [PATCH 32/67] * I2CP over SSL, enable with i2cp.SSL=true --- .../net/i2p/client/I2CPSSLSocketFactory.java | 183 ++++++++++++ .../src/net/i2p/client/I2PSessionImpl.java | 14 +- .../src/net/i2p/client/I2PSimpleSession.java | 6 +- .../router/client/ClientListenerRunner.java | 58 ++-- .../net/i2p/router/client/ClientManager.java | 20 +- .../client/SSLClientListenerRunner.java | 282 ++++++++++++++++++ 6 files changed, 526 insertions(+), 37 deletions(-) create mode 100644 core/java/src/net/i2p/client/I2CPSSLSocketFactory.java create mode 100644 router/java/src/net/i2p/router/client/SSLClientListenerRunner.java diff --git a/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java b/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java new file mode 100644 index 000000000..d562388f1 --- /dev/null +++ b/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java @@ -0,0 +1,183 @@ +package net.i2p.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.security.KeyStore; +import java.security.GeneralSecurityException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import net.i2p.I2PAppContext; +import net.i2p.util.Log; + +/** + * Loads trusted ASCII certs from ~/.i2p/certificates/ and $CWD/certificates/. + * Keeps a single static SSLContext for the whole JVM. + * + * @author zzz + * @since 0.8.3 + */ +class I2CPSSLSocketFactory { + + private static final Object _initLock = new Object(); + private static SSLSocketFactory _factory; + private static Log _log; + + private static final String CERT_DIR = "certificates"; + + /** + * Initializes the static SSL Context if required, then returns a socket + * to the host. + * + * @param ctx just for logging + * @throws IOException on init error or usual socket errors + */ + public static Socket createSocket(I2PAppContext ctx, String host, int port) throws IOException { + synchronized(_initLock) { + if (_factory == null) { + _log = ctx.logManager().getLog(I2CPSSLSocketFactory.class); + initSSLContext(ctx); + if (_factory == null) + throw new IOException("Unable to create SSL Context for I2CP Client"); + _log.info("I2CP Client-side SSL Context initialized"); + } + } + return _factory.createSocket(host, port); + } + + /** + * Loads certs from + * the ~/.i2p/certificates/ and $CWD/certificates/ directories. + */ + private static void initSSLContext(I2PAppContext context) { + KeyStore ks; + try { + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(null, "".toCharArray()); + } catch (GeneralSecurityException gse) { + _log.error("Key Store init error", gse); + return; + } catch (IOException ioe) { + _log.error("Key Store init error", ioe); + return; + } + + File dir = new File(context.getConfigDir(), CERT_DIR); + int adds = addCerts(dir, ks); + int totalAdds = adds; + if (adds > 0 && _log.shouldLog(Log.INFO)) + _log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + + File dir2 = new File(System.getProperty("user.dir"), CERT_DIR); + if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) { + adds = addCerts(dir2, ks); + totalAdds += adds; + if (adds > 0 && _log.shouldLog(Log.INFO)) + _log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + } + if (totalAdds > 0) { + if (_log.shouldLog(Log.INFO)) + _log.info("Loaded total of " + totalAdds + " new trusted certificates"); + } else { + _log.error("No trusted certificates loaded (looked in " + + dir.getAbsolutePath() + (dir.getAbsolutePath().equals(dir2.getAbsolutePath()) ? "" : (" and " + dir2.getAbsolutePath())) + + ", I2CP SSL client connections will fail. " + + "Copy the file certificates/i2cp.local.crt from the router to the directory."); + // don't continue, since we didn't load the system keystore, we have nothing. + return; + } + + try { + SSLContext sslc = SSLContext.getInstance("TLS"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + sslc.init(null, tmf.getTrustManagers(), context.random()); + _factory = sslc.getSocketFactory(); + } catch (GeneralSecurityException gse) { + _log.error("SSL context init error", gse); + } + } + + /** + * Load all X509 Certs from a directory and add them to the + * trusted set of certificates in the key store + * + * @return number successfully added + */ + private static int addCerts(File dir, KeyStore ks) { + if (_log.shouldLog(Log.INFO)) + _log.info("Looking for X509 Certificates in " + dir.getAbsolutePath()); + int added = 0; + if (dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + File f = files[i]; + if (!f.isFile()) + continue; + // use file name as alias + String alias = f.getName().toLowerCase(); + boolean success = addCert(f, alias, ks); + if (success) + added++; + } + } + } + return added; + } + + /** + * Load an X509 Cert from a file and add it to the + * trusted set of certificates in the key store + * + * @return success + */ + private static boolean addCert(File file, String alias, KeyStore ks) { + InputStream fis = null; + try { + fis = new FileInputStream(file); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate)cf.generateCertificate(fis); + if (_log.shouldLog(Log.INFO)) { + _log.info("Read X509 Certificate from " + file.getAbsolutePath() + + " Issuer: " + cert.getIssuerX500Principal() + + "; Valid From: " + cert.getNotBefore() + + " To: " + cert.getNotAfter()); + } + try { + cert.checkValidity(); + } catch (CertificateExpiredException cee) { + _log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee); + return false; + } catch (CertificateNotYetValidException cnyve) { + _log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve); + return false; + } + ks.setCertificateEntry(alias, cert); + if (_log.shouldLog(Log.INFO)) + _log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal()); + } catch (GeneralSecurityException gse) { + _log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse); + return false; + } catch (IOException ioe) { + _log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe); + return false; + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} + } + return true; + } +} diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 4b65d422b..35c205d50 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -131,6 +131,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa private long _lastActivity; private boolean _isReduced; + /** SSL interface (only) @since 0.8.3 */ + protected static final String PROP_ENABLE_SSL = "i2cp.SSL"; + void dateUpdated() { _dateReceived = true; synchronized (_dateReceivedLock) { @@ -181,7 +184,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa protected void loadConfig(Properties options) { _options = new Properties(); _options.putAll(filter(options)); - if (!_context.isRouterContext()) { + if (_context.isRouterContext()) { + // just for logging + _hostname = "[internal connection]"; + } else { _hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + ""); try { @@ -195,6 +201,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } // auto-add auth if required, not set in the options, and we are in the same JVM + // TODO bypass this on router side for internal connections if (_context.isRouterContext() && Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue() && ((!options.containsKey("i2cp.username")) || (!options.containsKey("i2cp.password")))) { @@ -302,7 +309,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa _queue = mgr.connect(); _reader = new QueuedI2CPMessageReader(_queue, this); } else { - _socket = new Socket(_hostname, _portNum); + if (Boolean.valueOf(_options.getProperty(PROP_ENABLE_SSL)).booleanValue()) + _socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum); + else + _socket = new Socket(_hostname, _portNum); // _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it. _out = _socket.getOutputStream(); synchronized (_out) { diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index 4f15c16ff..ed9ec5cc3 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -23,7 +23,6 @@ import net.i2p.internal.I2CPMessageQueue; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PAppThread; -import net.i2p.util.InternalSocket; /** * Create a new session for doing naming and bandwidth queries only. Do not create a Destination. @@ -80,7 +79,10 @@ class I2PSimpleSession extends I2PSessionImpl2 { _queue = mgr.connect(); _reader = new QueuedI2CPMessageReader(_queue, this); } else { - _socket = new Socket(_hostname, _portNum); + if (Boolean.valueOf(getOptions().getProperty(PROP_ENABLE_SSL)).booleanValue()) + _socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum); + else + _socket = new Socket(_hostname, _portNum); _out = _socket.getOutputStream(); synchronized (_out) { _out.write(I2PClient.PROTOCOL_BYTE); diff --git a/router/java/src/net/i2p/router/client/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java index 4e0a91aee..5dc5c6506 100644 --- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java @@ -25,12 +25,12 @@ import net.i2p.util.Log; * @author jrandom */ class ClientListenerRunner implements Runnable { - protected Log _log; - protected RouterContext _context; - protected ClientManager _manager; + protected final Log _log; + protected final RouterContext _context; + protected final ClientManager _manager; protected ServerSocket _socket; - protected int _port; - private boolean _bindAllInterfaces; + protected final int _port; + protected final boolean _bindAllInterfaces; protected boolean _running; protected boolean _listening; @@ -38,18 +38,33 @@ class ClientListenerRunner implements Runnable { public ClientListenerRunner(RouterContext context, ClientManager manager, int port) { _context = context; - _log = _context.logManager().getLog(ClientListenerRunner.class); + _log = _context.logManager().getLog(getClass()); _manager = manager; _port = port; - - String val = context.getProperty(BIND_ALL_INTERFACES); - _bindAllInterfaces = Boolean.valueOf(val).booleanValue(); + _bindAllInterfaces = context.getBooleanProperty(BIND_ALL_INTERFACES); } - public void setPort(int port) { _port = port; } - public int getPort() { return _port; } public boolean isListening() { return _running && _listening; } + /** + * Get a ServerSocket. + * Split out so it can be overridden for SSL. + * @since 0.8.3 + */ + protected ServerSocket getServerSocket() throws IOException { + if (_bindAllInterfaces) { + if (_log.shouldLog(Log.INFO)) + _log.info("Listening on port " + _port + " on all interfaces"); + return new ServerSocket(_port); + } else { + String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST, + ClientManagerFacadeImpl.DEFAULT_HOST); + if (_log.shouldLog(Log.INFO)) + _log.info("Listening on port " + _port + " of the specific interface: " + listenInterface); + return new ServerSocket(_port, 0, InetAddress.getByName(listenInterface)); + } + } + /** * Start up the socket listener, listens for connections, and * fires those connections off via {@link #runConnection runConnection}. @@ -62,18 +77,7 @@ class ClientListenerRunner implements Runnable { int curDelay = 1000; while (_running) { try { - if (_bindAllInterfaces) { - if (_log.shouldLog(Log.INFO)) - _log.info("Listening on port " + _port + " on all interfaces"); - _socket = new ServerSocket(_port); - } else { - String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST, - ClientManagerFacadeImpl.DEFAULT_HOST); - if (_log.shouldLog(Log.INFO)) - _log.info("Listening on port " + _port + " of the specific interface: " + listenInterface); - _socket = new ServerSocket(_port, 0, InetAddress.getByName(listenInterface)); - } - + _socket = getServerSocket(); if (_log.shouldLog(Log.DEBUG)) _log.debug("ServerSocket created, before accept: " + _socket); @@ -131,7 +135,8 @@ class ClientListenerRunner implements Runnable { } /** give the i2cp client 5 seconds to show that they're really i2cp clients */ - private final static int CONNECT_TIMEOUT = 5*1000; + protected final static int CONNECT_TIMEOUT = 5*1000; + private final static int LOOP_DELAY = 250; /** * Verify the first byte. @@ -141,16 +146,17 @@ class ClientListenerRunner implements Runnable { protected boolean validate(Socket socket) { try { InputStream is = socket.getInputStream(); - for (int i = 0; i < 20; i++) { + for (int i = 0; i < CONNECT_TIMEOUT / LOOP_DELAY; i++) { if (is.available() > 0) return is.read() == I2PClient.PROTOCOL_BYTE; - try { Thread.sleep(250); } catch (InterruptedException ie) {} + try { Thread.sleep(LOOP_DELAY); } catch (InterruptedException ie) {} } } catch (IOException ioe) {} if (_log.shouldLog(Log.WARN)) _log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping"); return false; } + /** * Handle the connection by passing it off to a {@link ClientConnectionRunner ClientConnectionRunner} * diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index 2a3a4c6ed..a534bdfb1 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -53,6 +53,8 @@ class ClientManager { /** Disable external interface, allow internal clients only @since 0.8.3 */ private static final String PROP_DISABLE_EXTERNAL = "i2cp.disableInterface"; + /** SSL interface (only) @since 0.8.3 */ + private static final String PROP_ENABLE_SSL = "i2cp.SSL"; /** ms to wait before rechecking for inbound messages to deliver to clients */ private final static int INBOUND_POLL_INTERVAL = 300; @@ -60,10 +62,10 @@ class ClientManager { public ClientManager(RouterContext context, int port) { _ctx = context; _log = context.logManager().getLog(ClientManager.class); - _ctx.statManager().createRateStat("client.receiveMessageSize", - "How large are messages received by the client?", - "ClientMessages", - new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + //_ctx.statManager().createRateStat("client.receiveMessageSize", + // "How large are messages received by the client?", + // "ClientMessages", + // new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); _runners = new HashMap(); _pendingRunners = new HashSet(); startListeners(port); @@ -72,7 +74,11 @@ class ClientManager { /** Todo: Start a 3rd listener for IPV6? */ private void startListeners(int port) { if (!_ctx.getBooleanProperty(PROP_DISABLE_EXTERNAL)) { - _listener = new ClientListenerRunner(_ctx, this, port); + // there's no option to start both an SSL and non-SSL listener + if (_ctx.getBooleanProperty(PROP_ENABLE_SSL)) + _listener = new SSLClientListenerRunner(_ctx, this, port); + else + _listener = new ClientListenerRunner(_ctx, this, port); Thread t = new I2PThread(_listener, "ClientListener:" + port, true); t.start(); } @@ -494,8 +500,8 @@ class ClientManager { runner = getRunner(_msg.getDestinationHash()); if (runner != null) { - _ctx.statManager().addRateData("client.receiveMessageSize", - _msg.getPayload().getSize(), 0); + //_ctx.statManager().addRateData("client.receiveMessageSize", + // _msg.getPayload().getSize(), 0); runner.receiveMessage(_msg.getDestination(), null, _msg.getPayload()); } else { // no client connection... diff --git a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java new file mode 100644 index 000000000..0dc053a33 --- /dev/null +++ b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java @@ -0,0 +1,282 @@ +package net.i2p.router.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.ServerSocket; +import java.security.KeyStore; +import java.security.GeneralSecurityException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.Arrays; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLContext; + +import net.i2p.client.I2PClient; +import net.i2p.data.Base32; +import net.i2p.data.Base64; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; +import net.i2p.util.ShellCommand; + +/** + * SSL version of ClientListenerRunner + * + * @since 0.8.3 + * @author zzz + */ +class SSLClientListenerRunner extends ClientListenerRunner { + + private SSLServerSocketFactory _factory; + + private static final String PROP_KEYSTORE_PASSWORD = "i2cp.keystorePassword"; + private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit"; + private static final String PROP_KEY_PASSWORD = "i2cp.keyPassword"; + private static final String KEY_ALIAS = "i2cp"; + private static final String ASCII_KEYFILE = "i2cp.local.crt"; + + public SSLClientListenerRunner(RouterContext context, ClientManager manager, int port) { + super(context, manager, port); + } + + /** + * @return success if it exists and we have a password, or it was created successfully. + */ + private boolean verifyKeyStore(File ks) { + if (ks.exists()) { + boolean rv = _context.getProperty(PROP_KEY_PASSWORD) != null; + if (!rv) + _log.error("I2CP SSL error, must set " + PROP_KEY_PASSWORD + " in " + + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath()); + return rv; + } + File dir = ks.getParentFile(); + if (!dir.exists()) { + File sdir = new SecureDirectory(dir.getAbsolutePath()); + if (!sdir.mkdir()) + return false; + } + boolean rv = createKeyStore(ks); + + // Now read it back out of the new keystore and save it in ascii form + // where the clients can get to it. + // Failure of this part is not fatal. + if (rv) + exportCert(ks); + return rv; + } + + + /** + * Call out to keytool to create a new keystore with a keypair in it. + * Trying to do this programatically is a nightmare, requiring either BouncyCastle + * libs or using proprietary Sun libs, and it's a huge mess. + * If successful, stores the keystore password and key password in router.config. + * + * @return success + */ + private boolean createKeyStore(File ks) { + // make a random 48 character password (30 * 8 / 5) + byte[] rand = new byte[30]; + _context.random().nextBytes(rand); + String keyPassword = Base32.encode(rand); + // and one for the cname + _context.random().nextBytes(rand); + String cname = Base32.encode(rand) + ".i2cp.i2p.net"; + + String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath(); + String[] args = new String[] { + keytool, + "-genkey", // -genkeypair preferred in newer keytools, but this works with more + "-storetype", KeyStore.getDefaultType(), + "-keystore", ks.getAbsolutePath(), + "-storepass", DEFAULT_KEYSTORE_PASSWORD, + "-alias", KEY_ALIAS, + "-dname", "CN=" + cname + ",OU=I2CP,O=I2P Anonymous Network,L=XX,ST=XX,C=XX", + "-validity", "3652", // 10 years + "-keyalg", "DSA", + "-keysize", "1024", + "-keypass", keyPassword}; + boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs + if (success) { + success = ks.exists(); + if (success) { + SecureFileOutputStream.setPerms(ks); + _context.router().setConfigSetting(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + _context.router().setConfigSetting(PROP_KEY_PASSWORD, keyPassword); + _context.router().saveConfig(); + } + } + if (success) { + _log.logAlways(Log.INFO, "Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" + + "The certificate name was generated randomly, and is not associated with your " + + "IP address, host name, router identity, or destination keys."); + } else { + _log.error("Failed to create I2CP SSL keystore using command line:"); + StringBuilder buf = new StringBuilder(256); + for (int i = 0; i < args.length; i++) { + buf.append('"').append(args[i]).append("\" "); + } + _log.error(buf.toString()); + _log.error("This is for the Sun/Oracle keytool, others may be incompatible.\n" + + "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD + + " to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath()); + } + return success; + } + + /** + * Pull the cert back OUT of the keystore and save it as ascii + * so the clients can get to it. + */ + private void exportCert(File ks) { + File sdir = new SecureDirectory(_context.getConfigDir(), "certificates"); + if (sdir.exists() || sdir.mkdir()) { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream fis = new FileInputStream(ks); + String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + keyStore.load(fis, ksPass.toCharArray()); + fis.close(); + Certificate cert = keyStore.getCertificate(KEY_ALIAS); + if (cert != null) { + File certFile = new File(sdir, ASCII_KEYFILE); + saveCert(cert, certFile); + } else { + _log.error("Error getting SSL cert to save as ASCII"); + } + } catch (GeneralSecurityException gse) { + _log.error("Error saving ASCII SSL keys", gse); + } catch (IOException ioe) { + _log.error("Error saving ASCII SSL keys", ioe); + } + } else { + _log.error("Error saving ASCII SSL keys"); + } + } + + private static final int LINE_LENGTH = 64; + + /** + * Modified from: + * http://www.exampledepot.com/egs/java.security.cert/ExportCert.html + * + * Write a certificate to a file in base64 format. + */ + private void saveCert(Certificate cert, File file) { + OutputStream os = null; + try { + // Get the encoded form which is suitable for exporting + byte[] buf = cert.getEncoded(); + os = new SecureFileOutputStream(file); + PrintWriter wr = new PrintWriter(os); + wr.println("-----BEGIN CERTIFICATE-----"); + String b64 = Base64.encode(buf, true); // true = use standard alphabet + for (int i = 0; i < b64.length(); i += LINE_LENGTH) { + wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length()))); + } + wr.println("-----END CERTIFICATE-----"); + wr.flush(); + } catch (CertificateEncodingException cee) { + _log.error("Error writing X509 Certificate " + file.getAbsolutePath(), cee); + } catch (IOException ioe) { + _log.error("Error writing X509 Certificate " + file.getAbsolutePath(), ioe); + } finally { + try { if (os != null) os.close(); } catch (IOException foo) {} + } + } + + /** + * Sets up the SSLContext and sets the socket factory. + * @return success + */ + private boolean initializeFactory(File ks) { + String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); + String keyPass = _context.getProperty(PROP_KEY_PASSWORD); + if (keyPass == null) { + _log.error("No key password, set " + PROP_KEY_PASSWORD + + " in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath()); + return false; + } + try { + SSLContext sslc = SSLContext.getInstance("TLS"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream fis = new FileInputStream(ks); + keyStore.load(fis, ksPass.toCharArray()); + fis.close(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyPass.toCharArray()); + sslc.init(kmf.getKeyManagers(), null, _context.random()); + _factory = sslc.getServerSocketFactory(); + return true; + } catch (GeneralSecurityException gse) { + _log.error("Error loading SSL keys", gse); + } catch (IOException ioe) { + _log.error("Error loading SSL keys", ioe); + } + return false; + } + + /** + * Get a SSLServerSocket. + */ + @Override + protected ServerSocket getServerSocket() throws IOException { + ServerSocket rv; + if (_bindAllInterfaces) { + if (_log.shouldLog(Log.INFO)) + _log.info("Listening on port " + _port + " on all interfaces"); + rv = _factory.createServerSocket(_port); + } else { + String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST, + ClientManagerFacadeImpl.DEFAULT_HOST); + if (_log.shouldLog(Log.INFO)) + _log.info("Listening on port " + _port + " of the specific interface: " + listenInterface); + rv = _factory.createServerSocket(_port, 0, InetAddress.getByName(listenInterface)); + } + return rv; + } + + /** + * Create (if necessary) and load the key store, then run. + */ + @Override + public void runServer() { + File keyStore = new File(_context.getConfigDir(), "keystore/i2cp.ks"); + if (verifyKeyStore(keyStore) && initializeFactory(keyStore)) { + super.runServer(); + } else { + _log.error("SSL I2CP server error - Failed to create or open key store"); + } + } + + /** + * Overridden because SSL handshake may need more time, + * and available() in super doesn't work. + * The handshake doesn't start until a read(). + */ + @Override + protected boolean validate(Socket socket) { + try { + InputStream is = socket.getInputStream(); + int oldTimeout = socket.getSoTimeout(); + socket.setSoTimeout(4 * CONNECT_TIMEOUT); + boolean rv = is.read() == I2PClient.PROTOCOL_BYTE; + socket.setSoTimeout(oldTimeout); + return rv; + } catch (IOException ioe) {} + if (_log.shouldLog(Log.WARN)) + _log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping"); + return false; + } +} From c44c22255707297501423ae23b3e2f79abbc9df8 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 14:34:27 +0000 Subject: [PATCH 33/67] minor --- .../java/src/net/i2p/router/web/RouterConsoleRunner.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 7f415894d..870f162d4 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.security.KeyStore; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; @@ -87,7 +88,7 @@ public class RouterConsoleRunner { */ public RouterConsoleRunner(String args[]) { if (args.length == 0) { - // _listenHost and _webAppsDir are defaulted above + // _listenHost and _webAppsDir are defaulted below _listenPort = DEFAULT_LISTEN_PORT; } else { boolean ssl = false; @@ -352,7 +353,7 @@ public class RouterConsoleRunner { String[] args = new String[] { keytool, "-genkey", // -genkeypair preferred in newer keytools, but this works with more - "-storetype", "JKS", + "-storetype", KeyStore.getDefaultType(), "-keystore", ks.getAbsolutePath(), "-storepass", DEFAULT_KEYSTORE_PASSWORD, "-alias", "console", From e772107c580b2bf9df4de5fd511506ca9f675347 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 14:34:50 +0000 Subject: [PATCH 34/67] trivial cleanup --- core/java/src/net/i2p/internal/PoisonI2CPMessage.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/java/src/net/i2p/internal/PoisonI2CPMessage.java b/core/java/src/net/i2p/internal/PoisonI2CPMessage.java index 23b99db9a..dda1301ee 100644 --- a/core/java/src/net/i2p/internal/PoisonI2CPMessage.java +++ b/core/java/src/net/i2p/internal/PoisonI2CPMessage.java @@ -51,8 +51,6 @@ public class PoisonI2CPMessage extends I2CPMessageImpl { @Override public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("[PoisonMessage]"); - return buf.toString(); + return "[PoisonMessage]"; } } From 6826c1eb6984d59c796ac9cff646d771a9c4f1f6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 20:30:00 +0000 Subject: [PATCH 35/67] disable I2CP auth in-JVM --- .../src/net/i2p/client/I2PSessionImpl.java | 5 ++--- .../router/client/ClientConnectionRunner.java | 2 +- .../client/ClientMessageEventListener.java | 18 ++++++++++-------- .../client/QueuedClientConnectionRunner.java | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 35c205d50..ab2c48904 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -200,9 +200,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa } } - // auto-add auth if required, not set in the options, and we are in the same JVM - // TODO bypass this on router side for internal connections - if (_context.isRouterContext() && + // auto-add auth if required, not set in the options, and we are not in the same JVM + if ((!_context.isRouterContext()) && Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue() && ((!options.containsKey("i2cp.username")) || (!options.containsKey("i2cp.password")))) { String configUser = _context.getProperty("i2cp.username"); diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index 0b207da53..8bef2776d 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -109,7 +109,7 @@ class ClientConnectionRunner { */ public void startRunning() { try { - _reader = new I2CPMessageReader(_socket.getInputStream(), new ClientMessageEventListener(_context, this)); + _reader = new I2CPMessageReader(_socket.getInputStream(), new ClientMessageEventListener(_context, this, true)); _writer = new ClientWriterRunner(_context, this); I2PThread t = new I2PThread(_writer); t.setName("I2CP Writer " + ++__id); diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index edaefc599..d45df2cdb 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -42,14 +42,19 @@ import net.i2p.util.RandomSource; * */ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventListener { - private Log _log; - private RouterContext _context; - private ClientConnectionRunner _runner; + private final Log _log; + private final RouterContext _context; + private final ClientConnectionRunner _runner; + private final boolean _enforceAuth; - public ClientMessageEventListener(RouterContext context, ClientConnectionRunner runner) { + /** + * @param enforceAuth set false for in-JVM, true for socket access + */ + public ClientMessageEventListener(RouterContext context, ClientConnectionRunner runner, boolean enforceAuth) { _context = context; _log = _context.logManager().getLog(ClientMessageEventListener.class); _runner = runner; + _enforceAuth = enforceAuth; _context.statManager().createRateStat("client.distributeTime", "How long it took to inject the client message into the router", "ClientMessages", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); } @@ -153,10 +158,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } // Auth, since 0.8.2 - // In-JVM accesses have access to the same context properties, so - // they will be set on the client side... therefore we don't need to pass in - // some indication of (socket instanceof InternalSocket) - if (Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue()) { + if (_enforceAuth && Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue()) { String configUser = _context.getProperty("i2cp.username"); String configPW = _context.getProperty("i2cp.password"); if (configUser != null && configPW != null) { diff --git a/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java index 4eec8e531..758e8221e 100644 --- a/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/QueuedClientConnectionRunner.java @@ -35,7 +35,7 @@ class QueuedClientConnectionRunner extends ClientConnectionRunner { */ @Override public void startRunning() { - _reader = new QueuedI2CPMessageReader(this.queue, new ClientMessageEventListener(_context, this)); + _reader = new QueuedI2CPMessageReader(this.queue, new ClientMessageEventListener(_context, this, false)); _reader.startReading(); } From 5ed8be24663835b045ac7da6ccb0bac3175bfce1 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 21:39:29 +0000 Subject: [PATCH 36/67] add conn limits to form --- .../src/net/i2p/i2ptunnel/web/EditBean.java | 25 +++++++++++ .../src/net/i2p/i2ptunnel/web/IndexBean.java | 42 ++++++++++++++++++- apps/i2ptunnel/jsp/editServer.jsp | 38 ++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 7c753e6ab..ebaeccf19 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -234,6 +234,31 @@ public class EditBean extends IndexBean { return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, ""); } + /** all of these are @since 0.8.3 */ + public String getLimitMinute(int tunnel) { + return getProperty(tunnel, PROP_MAX_CONNS_MIN, "0"); + } + + public String getLimitHour(int tunnel) { + return getProperty(tunnel, PROP_MAX_CONNS_HOUR, "0"); + } + + public String getLimitDay(int tunnel) { + return getProperty(tunnel, PROP_MAX_CONNS_DAY, "0"); + } + + public String getTotalMinute(int tunnel) { + return getProperty(tunnel, PROP_MAX_TOTAL_CONNS_MIN, "0"); + } + + public String getTotalHour(int tunnel) { + return getProperty(tunnel, PROP_MAX_TOTAL_CONNS_HOUR, "0"); + } + + public String getTotalDay(int tunnel) { + return getProperty(tunnel, PROP_MAX_TOTAL_CONNS_DAY, "0"); + } + private int getProperty(int tunnel, String prop, int def) { TunnelController tun = getController(tunnel); if (tun != null) { 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 5e38b2b4e..909e94c23 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -712,6 +712,44 @@ public class IndexBean { _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim()); } + /** all of these are @since 0.8.3 */ + protected static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute"; + protected static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour"; + protected static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay"; + protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute"; + protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour"; + protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay"; + + public void setLimitMinute(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_MIN, s.trim()); + } + + public void setLimitHour(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_HOUR, s.trim()); + } + + public void setLimitDay(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_DAY, s.trim()); + } + + public void setTotalMinute(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_MIN, s.trim()); + } + + public void setTotalHour(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_HOUR, s.trim()); + } + + public void setTotalDay(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim()); + } + /** params needed for hashcash and dest modification */ public void setEffort(String val) { if (val != null) { @@ -911,7 +949,9 @@ public class IndexBean { "proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword" }; private static final String _otherServerOpts[] = { - "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList" + "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", + PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY, + PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY }; protected static final Set _noShowSet = new HashSet(); static { diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 5d3660453..4ecd93e64 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -368,10 +368,46 @@ <label for="accessList" accesskey="s"> <%=intl._("Access List")%>: </label> - <textarea rows="2" style="height: 4em;" cols="60" id="hostField" name="accessList" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea> + <textarea rows="2" style="height: 6em;" cols="60" id="hostField" name="accessList" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea> <span class="comment"><%=intl._("(Restrict to these clients only)")%></span> </div> + <div class="subdivider"> + <hr /> + </div> + + <div class="rowItem"> + <div id="optionsField" class="rowItem"> + <label><%=intl._("Inbound connection limits (0 to disable)")%><br><%=intl._("Per client")%>:</label> + </div> + <div id="portField" class="rowItem"> + <label><%=intl._("Per minute")%>:</label> + <input type="text" id="port" name="limitMinute" value="<%=editBean.getLimitMinute(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <label><%=intl._("Per hour")%>:</label> + <input type="text" id="port" name="limitHour" value="<%=editBean.getLimitHour(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <label><%=intl._("Per day")%>:</label> + <input type="text" id="port" name="limitDay" value="<%=editBean.getLimitDay(curTunnel)%>" class="freetext" /> + </div> + </div> + <div class="rowItem"> + <div id="optionsField" class="rowItem"> + <label><%=intl._("Total")%>:</label> + </div> + <div id="portField" class="rowItem"> + <input type="text" id="port" name="totalMinute" value="<%=editBean.getTotalMinute(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <input type="text" id="port" name="totalHour" value="<%=editBean.getTotalHour(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <input type="text" id="port" name="totalDay" value="<%=editBean.getTotalDay(curTunnel)%>" class="freetext" /> + </div> + </div> + <div class="subdivider"> <hr /> </div> From c15c97f69cdeb034fe90b6da9c139f3c8bcec22d Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 14 Dec 2010 22:14:29 +0000 Subject: [PATCH 37/67] put blacklist mode on form --- .../java/src/net/i2p/i2ptunnel/web/EditBean.java | 8 ++++++-- .../java/src/net/i2p/i2ptunnel/web/IndexBean.java | 14 +++++++++++--- apps/i2ptunnel/jsp/editServer.jsp | 13 +++++++------ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index ebaeccf19..696c652e2 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -171,8 +171,12 @@ public class EditBean extends IndexBean { return getProperty(tunnel, "i2cp.leaseSetKey", ""); } - public boolean getAccess(int tunnel) { - return getBooleanProperty(tunnel, "i2cp.enableAccessList"); + public String getAccessMode(int tunnel) { + if (getBooleanProperty(tunnel, PROP_ENABLE_ACCESS_LIST)) + return "1"; + if (getBooleanProperty(tunnel, PROP_ENABLE_BLACKLIST)) + return "2"; + return "0"; } public String getAccessList(int tunnel) { 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 909e94c23..510f55033 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -643,9 +643,17 @@ public class IndexBean { public void setEncrypt(String moo) { _booleanOptions.add("i2cp.encryptLeaseSet"); } - public void setAccess(String moo) { - _booleanOptions.add("i2cp.enableAccessList"); + + protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList"; + protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList"; + + public void setAccessMode(String val) { + if ("1".equals(val)) + _booleanOptions.add(PROP_ENABLE_ACCESS_LIST); + else if ("2".equals(val)) + _booleanOptions.add(PROP_ENABLE_BLACKLIST); } + public void setDelayOpen(String moo) { _booleanOptions.add("i2cp.delayOpen"); } @@ -942,7 +950,7 @@ public class IndexBean { I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH }; private static final String _booleanServerOpts[] = { - "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", "i2cp.enableAccessList" + "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST }; private static final String _otherClientOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 4ecd93e64..5ad17a2ef 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -359,17 +359,18 @@ </label> </div> <div id="portField" class="rowItem"> - <label for="access" accesskey="s"> - <%=intl._("Enable")%>: - </label> - <input value="1" type="checkbox" id="startOnLoad" name="access" title="Enable Access List"<%=(editBean.getAccess(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + <label><%=intl._("Disable")%></label> + <input value="0" type="radio" id="startOnLoad" name="accessMode" title="Allow all clients"<%=(editBean.getAccessMode(curTunnel).equals("0") ? " checked=\"checked\"" : "")%> class="tickbox" /> + <label><%=intl._("Whitelist")%></label> + <input value="1" type="radio" id="startOnLoad" name="accessMode" title="Allow listed clients only"<%=(editBean.getAccessMode(curTunnel).equals("1") ? " checked=\"checked\"" : "")%> class="tickbox" /> + <label><%=intl._("Blacklist")%></label> + <input value="2" type="radio" id="startOnLoad" name="accessMode" title="Reject listed clients"<%=(editBean.getAccessMode(curTunnel).equals("2") ? " checked=\"checked\"" : "")%> class="tickbox" /> </div> <div id="hostField" class="rowItem"> <label for="accessList" accesskey="s"> <%=intl._("Access List")%>: </label> - <textarea rows="2" style="height: 6em;" cols="60" id="hostField" name="accessList" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea> - <span class="comment"><%=intl._("(Restrict to these clients only)")%></span> + <textarea rows="2" style="height: 8em;" cols="60" id="hostField" name="accessList" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea> </div> <div class="subdivider"> From 9d41e86866e2e94630c673fde84d27b671511a2b Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 01:06:08 +0000 Subject: [PATCH 38/67] add jump list to form --- .../src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java | 2 +- .../java/src/net/i2p/i2ptunnel/web/EditBean.java | 6 ++++++ .../java/src/net/i2p/i2ptunnel/web/IndexBean.java | 11 ++++++++++- apps/i2ptunnel/jsp/editClient.jsp | 12 ++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 6c5c38e5e..f747e48e7 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -901,7 +901,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } } - private static String DEFAULT_JUMP_SERVERS = + public static final String DEFAULT_JUMP_SERVERS = "http://i2host.i2p/cgi-bin/i2hostjump?," + "http://stats.i2p/cgi-bin/jump.cgi?a=," + "http://i2jump.i2p/"; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 696c652e2..994f026dc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -18,6 +18,7 @@ import net.i2p.data.Destination; import net.i2p.data.PrivateKeyFile; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; +import net.i2p.i2ptunnel.I2PTunnelHTTPClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; @@ -183,6 +184,11 @@ public class EditBean extends IndexBean { return getProperty(tunnel, "i2cp.accessList", "").replace(",", "\n"); } + public String getJumpList(int tunnel) { + return getProperty(tunnel, I2PTunnelHTTPClient.PROP_JUMP_SERVERS, + I2PTunnelHTTPClient.DEFAULT_JUMP_SERVERS).replace(",", "\n"); + } + public boolean getClose(int tunnel) { return getBooleanProperty(tunnel, "i2cp.closeOnIdle"); } 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 510f55033..39e85d986 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -24,6 +24,7 @@ import net.i2p.data.Certificate; import net.i2p.data.Destination; import net.i2p.data.PrivateKeyFile; import net.i2p.data.SessionKey; +import net.i2p.i2ptunnel.I2PTunnelHTTPClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; @@ -679,10 +680,17 @@ public class IndexBean { if (val != null) _otherOptions.put("i2cp.leaseSetKey", val.trim()); } + public void setAccessList(String val) { if (val != null) _otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",")); } + + public void setJumpList(String val) { + if (val != null) + _otherOptions.put(I2PTunnelHTTPClient.PROP_JUMP_SERVERS, val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",")); + } + public void setCloseTime(String val) { if (val != null) { try { @@ -954,7 +962,8 @@ public class IndexBean { }; private static final String _otherClientOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", - "proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword" + "proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword", + I2PTunnelHTTPClient.PROP_JUMP_SERVERS }; private static final String _otherServerOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index b6475f754..f2b427542 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -465,6 +465,18 @@ </div> <% } // httpclient || connect || socks || socksirc %> + <% if ("httpclient".equals(tunnelType)) { %> + <div id="optionsField" class="rowItem"> + <label><%=intl._("Jump URL List")%>:</label> + </div> + <div id="hostField" class="rowItem"> + <textarea rows="2" style="height: 8em;" cols="60" id="hostField" name="jumpList" title="List of helper URLs to offer when a host is not found in your addressbook" wrap="off"><%=editBean.getJumpList(curTunnel)%></textarea> + </div> + <div class="subdivider"> + <hr /> + </div> + <% } // httpclient %> + <div id="customOptionsField" class="rowItem"> <label for="customOptions" accesskey="u"> <%=intl._("Custom options")%>(<span class="accessKey">u</span>): From d000d2047d236d2c249f543922c3388091cf3607 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 01:21:58 +0000 Subject: [PATCH 39/67] clean up jump host verification --- .../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index f747e48e7..a9b1a8287 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -939,8 +939,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn // Skip jump servers we don't know String jumphost = jurl.substring(7); // "http://" jumphost = jumphost.substring(0, jumphost.indexOf('/')); - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost); - if (dest == null) continue; + if (!jumphost.endsWith(".i2p")) + continue; + if (!jumphost.endsWith(".b32.i2p")) { + Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost); + if (dest == null) continue; + } out.write("<br><a href=\"".getBytes()); out.write(jurl.getBytes()); From 77f910ee3611bb3571d9017bd1b4f6b5414e6009 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 13:41:11 +0000 Subject: [PATCH 40/67] add max conns to form --- .../java/src/net/i2p/i2ptunnel/web/EditBean.java | 4 ++++ .../java/src/net/i2p/i2ptunnel/web/IndexBean.java | 11 +++++++++-- apps/i2ptunnel/jsp/editServer.jsp | 10 +++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 994f026dc..87beb689c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -269,6 +269,10 @@ public class EditBean extends IndexBean { return getProperty(tunnel, PROP_MAX_TOTAL_CONNS_DAY, "0"); } + public String getMaxStreams(int tunnel) { + return getProperty(tunnel, PROP_MAX_STREAMS, "0"); + } + private int getProperty(int tunnel, String prop, int def) { TunnelController tun = getController(tunnel); if (tun != null) { 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 39e85d986..c419f184b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -735,6 +735,7 @@ public class IndexBean { protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute"; protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour"; protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay"; + protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams"; public void setLimitMinute(String s) { if (s != null) @@ -766,6 +767,11 @@ public class IndexBean { _otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim()); } + public void setMaxStreams(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_STREAMS, s.trim()); + } + /** params needed for hashcash and dest modification */ public void setEffort(String val) { if (val != null) { @@ -968,9 +974,10 @@ public class IndexBean { private static final String _otherServerOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY, - PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY + PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY, + PROP_MAX_STREAMS }; - protected static final Set _noShowSet = new HashSet(); + protected static final Set _noShowSet = new HashSet(64); static { _noShowSet.addAll(Arrays.asList(_noShowOpts)); _noShowSet.addAll(Arrays.asList(_booleanClientOpts)); diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 5ad17a2ef..b0f870fb7 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -333,7 +333,7 @@ <label for="encrypt" accesskey="e"> <%=intl._("Enable")%>: </label> - <input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="Encrypt LeaseSet"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + <input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="ONLY clients with the encryption key will be able to connect"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> </div> <div id="portField" class="rowItem"> <label for="encrypt" accesskey="e"> @@ -408,6 +408,14 @@ <input type="text" id="port" name="totalDay" value="<%=editBean.getTotalDay(curTunnel)%>" class="freetext" /> </div> </div> + <div class="rowItem"> + <div id="optionsField" class="rowItem"> + <label><%=intl._("Max concurrent connections (0 to disable)")%>:</label> + </div> + <div id="portField" class="rowItem"> + <input type="text" id="port" name="maxStreams" value="<%=editBean.getMaxStreams(curTunnel)%>" class="freetext" /> + </div> + </div> <div class="subdivider"> <hr /> From 8e709eec2eb956c2905ccd64d8dd6434714d827e Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 15:19:09 +0000 Subject: [PATCH 41/67] fixup after prop --- .../i2p/i2ptunnel/I2PTunnelHTTPServer.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index d5af6bfd0..a2305f1a4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -139,7 +139,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { if (allowGZIP && useGZIP) { I2PAppThread req = new I2PAppThread( - new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext()), + new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext(), _log), Thread.currentThread().getName()+".hc"); req.start(); } else { @@ -179,15 +179,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } private static class CompressedRequestor implements Runnable { - private Socket _webserver; - private I2PSocket _browser; - private String _headers; - private I2PAppContext _ctx; - public CompressedRequestor(Socket webserver, I2PSocket browser, String headers, I2PAppContext ctx) { + private final Socket _webserver; + private final I2PSocket _browser; + private final String _headers; + private final I2PAppContext _ctx; + // shadows _log in super() + private final Log _log; + + public CompressedRequestor(Socket webserver, I2PSocket browser, String headers, I2PAppContext ctx, Log log) { _webserver = webserver; _browser = browser; _headers = headers; _ctx = ctx; + _log = log; } public void run() { @@ -235,7 +239,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { String modifiedHeaders = formatHeaders(headers, command); compressedOut.write(modifiedHeaders.getBytes()); - Sender s = new Sender(compressedOut, serverin, "server: server to browser"); + Sender s = new Sender(compressedOut, serverin, "server: server to browser", _log); if (_log.shouldLog(Log.INFO)) _log.info("Before pumping the compressed response"); s.run(); // same thread @@ -378,8 +382,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { boolean ok = DataHelper.readLine(in, command); if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]"); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Read the http command [" + command.toString() + "]"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Read the http command [" + command.toString() + "]"); int trimmed = 0; if (command.length() > 0) { @@ -433,8 +437,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { } headers.setProperty(name, value); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Read the header [" + name + "] = [" + value + "]"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Read the header [" + name + "] = [" + value + "]"); } } } From 2deee2b1b7a4a3d14a1fbbe6c68183d37fe2863a Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 16:10:03 +0000 Subject: [PATCH 42/67] AES cleanups and javadoc --- core/java/src/net/i2p/crypto/AESEngine.java | 32 ++++++++++++------- .../src/net/i2p/crypto/CryptixAESEngine.java | 8 ++--- .../net/i2p/crypto/CryptixAESKeyCache.java | 7 ++++ .../src/net/i2p/crypto/ElGamalAESEngine.java | 12 ++++--- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/core/java/src/net/i2p/crypto/AESEngine.java b/core/java/src/net/i2p/crypto/AESEngine.java index fa6a9110a..6f78df529 100644 --- a/core/java/src/net/i2p/crypto/AESEngine.java +++ b/core/java/src/net/i2p/crypto/AESEngine.java @@ -22,13 +22,14 @@ import net.i2p.util.RandomSource; * See CryptixAESEngine for the real thing. */ public class AESEngine { - private Log _log; - private I2PAppContext _context; + protected final Log _log; + protected final I2PAppContext _context; + public AESEngine(I2PAppContext ctx) { _context = ctx; - _log = _context.logManager().getLog(AESEngine.class); - if (getClass() == AESEngine.class) - _log.warn("Warning: AES is disabled"); + _log = _context.logManager().getLog(getClass()); + if (getClass().equals(AESEngine.class)) + _log.logAlways(Log.WARN, "AES is disabled"); } /** Encrypt the payload with the session key @@ -44,7 +45,10 @@ public class AESEngine { encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length); } - /** Encrypt the payload with the session key + /** + * Encrypt the payload with the session key. + * This just copies payload to out, see extension for the real thing. + * * @param payload data to be encrypted * @param payloadIndex index into the payload to start encrypting * @param out where to store the result @@ -55,7 +59,7 @@ public class AESEngine { */ public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) { System.arraycopy(payload, payloadIndex, out, outIndex, length); - _log.warn("Warning: AES is disabled"); + _log.logAlways(Log.WARN, "AES is disabled"); } public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) { @@ -118,7 +122,6 @@ public class AESEngine { return data; } - /** Decrypt the data with the session key * @param payload data to be decrypted * @param payloadIndex index into the payload to start decrypting @@ -132,7 +135,10 @@ public class AESEngine { decrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length); } - /** Decrypt the data with the session key + /** + * Decrypt the data with the session key. + * This just copies payload to out, see extension for the real thing. + * * @param payload data to be decrypted * @param payloadIndex index into the payload to start decrypting * @param out where to store the cleartext @@ -143,18 +149,20 @@ public class AESEngine { */ public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) { System.arraycopy(payload, payloadIndex, out, outIndex, length); - _log.warn("Warning: AES is disabled"); + _log.logAlways(Log.WARN, "AES is disabled"); } /** - * Just copies payload to out + * This just copies payload to out, see extension for the real thing. * @param sessionKey unused */ public void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) { System.arraycopy(payload, inIndex, out, outIndex, out.length - outIndex); } - /** decrypt the data with the session key provided + /** + * This just copies payload to rv, see extension for the real thing. + * * @param payload encrypted data * @param sessionKey private session key */ diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java index acd2eb702..ea2338003 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java +++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java @@ -27,18 +27,16 @@ import net.i2p.util.Log; * @author jrandom, thecrypto */ public class CryptixAESEngine extends AESEngine { - private Log _log; private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm(); private final static boolean USE_FAKE_CRYPTO = false; - private final static byte FAKE_KEY = 0x2A; - private CryptixAESKeyCache _cache; + // keys are now cached in the SessionKey objects + //private CryptixAESKeyCache _cache; private static final ByteCache _prevCache = ByteCache.getInstance(16, 16); public CryptixAESEngine(I2PAppContext context) { super(context); - _log = context.logManager().getLog(CryptixAESEngine.class); - _cache = new CryptixAESKeyCache(); + //_cache = new CryptixAESKeyCache(); } /** @param length must be a multiple of 16 */ diff --git a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java index 5d39c1578..63b6dcfba 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java +++ b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java @@ -8,6 +8,8 @@ import java.util.concurrent.LinkedBlockingQueue; * data referenced in it is needed (which often is only one or two lines * of code) * + * Unused as a class, as the keys are cached in the SessionKey objects, + * but the static methods are used in FortunaStandalone. */ public final class CryptixAESKeyCache { private final LinkedBlockingQueue<KeyCacheEntry> _availableKeys; @@ -20,6 +22,9 @@ public final class CryptixAESKeyCache { private static final int MAX_KEYS = 64; + /* + * @deprecated unused, keys are now cached in the SessionKey objects + */ public CryptixAESKeyCache() { _availableKeys = new LinkedBlockingQueue(MAX_KEYS); } @@ -27,6 +32,7 @@ public final class CryptixAESKeyCache { /** * Get the next available structure, either from the cache or a brand new one * + * @deprecated unused, keys are now cached in the SessionKey objects */ public final KeyCacheEntry acquireKey() { KeyCacheEntry rv = _availableKeys.poll(); @@ -38,6 +44,7 @@ public final class CryptixAESKeyCache { /** * Put this structure back onto the available cache for reuse * + * @deprecated unused, keys are now cached in the SessionKey objects */ public final void releaseKey(KeyCacheEntry key) { _availableKeys.offer(key); diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index f4f276de2..11010f312 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -29,17 +29,17 @@ import net.i2p.util.Log; /** * Handles the actual ElGamal+AES encryption and decryption scenarios using the * supplied keys and data. + * + * No, this does not extend AESEngine or CryptixAESEngine. */ public class ElGamalAESEngine { - private final static Log _log = new Log(ElGamalAESEngine.class); + private final Log _log; private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size - private I2PAppContext _context; - - private ElGamalAESEngine() { // nop - } + private final I2PAppContext _context; public ElGamalAESEngine(I2PAppContext ctx) { _context = ctx; + _log = _context.logManager().getLog(ElGamalAESEngine.class); _context.statManager().createFrequencyStat("crypto.elGamalAES.encryptNewSession", "how frequently we encrypt to a new ElGamal/AES+SessionTag session?", @@ -627,6 +627,7 @@ public class ElGamalAESEngine { return numPadding; } +/**** public static void main(String args[]) { I2PAppContext ctx = new I2PAppContext(); ElGamalAESEngine e = new ElGamalAESEngine(ctx); @@ -656,4 +657,5 @@ public class ElGamalAESEngine { } } } +****/ } From 4c9558c586c8d1013edc9480228eb13b7f638206 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 17:08:11 +0000 Subject: [PATCH 43/67] adjust limits to reduce chance of running out; adjust limits based on max mem --- core/java/src/net/i2p/crypto/YKGenerator.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/core/java/src/net/i2p/crypto/YKGenerator.java b/core/java/src/net/i2p/crypto/YKGenerator.java index af83f2c92..165749a7c 100644 --- a/core/java/src/net/i2p/crypto/YKGenerator.java +++ b/core/java/src/net/i2p/crypto/YKGenerator.java @@ -41,7 +41,7 @@ class YKGenerator { private static final int MIN_NUM_BUILDERS; private static final int MAX_NUM_BUILDERS; private static final int CALC_DELAY; - private static final LinkedBlockingQueue<BigInteger[]> _values = new LinkedBlockingQueue(50); // list of BigInteger[] values (y and k) + private static final LinkedBlockingQueue<BigInteger[]> _values; private static final Thread _precalcThread; private static final I2PAppContext ctx; @@ -53,13 +53,21 @@ class YKGenerator { public final static int DEFAULT_YK_PRECALC_DELAY = 200; /** check every 30 seconds whether we have less than the minimum */ - private static long CHECK_DELAY = 30 * 1000; + private static long _checkDelay = 30 * 1000; static { ctx = I2PAppContext.getGlobalContext(); - MIN_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN); - MAX_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX); + + // add to the defaults for every 128MB of RAM, up to 1GB + long maxMemory = Runtime.getRuntime().maxMemory(); + int factor = Math.min(8, (int) (1 + (maxMemory / (128*1024*1024l)))); + int defaultMin = DEFAULT_YK_PRECALC_MIN * factor; + int defaultMax = DEFAULT_YK_PRECALC_MAX * factor; + MIN_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MIN, defaultMin); + MAX_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MAX, defaultMax); + CALC_DELAY = ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY); + _values = new LinkedBlockingQueue(MAX_NUM_BUILDERS); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " @@ -137,12 +145,13 @@ class YKGenerator { long endNeg = Clock.getInstance().now(); negTime += endNeg - startNeg; } + // 173ms each on a 2008 netbook System.out.println("YK fetch time for 5 runs: " + negTime + " @ " + negTime / 5l + "ms each"); } private static class YKPrecalcRunner implements Runnable { - private int _minSize; - private int _maxSize; + private final int _minSize; + private final int _maxSize; private YKPrecalcRunner(int minSize, int maxSize) { _minSize = minSize; @@ -155,10 +164,10 @@ class YKGenerator { //long start = Clock.getInstance().now(); int startSize = getSize(); // Adjust delay - if (startSize <= (_minSize / 2) && CHECK_DELAY > 1000) - CHECK_DELAY -= 1000; - else if (startSize > (_minSize * 2) && CHECK_DELAY < 60000) - CHECK_DELAY += 1000; + if (startSize <= (_minSize * 2 / 3) && _checkDelay > 1000) + _checkDelay -= 1000; + else if (startSize > (_minSize * 3 / 2) && _checkDelay < 60*1000) + _checkDelay += 1000; curSize = startSize; if (curSize < _minSize) { for (int i = curSize; i < _maxSize; i++) { @@ -183,7 +192,7 @@ class YKGenerator { // + (CALC_DELAY * numCalc) + "ms relief). now sleeping"); //} try { - Thread.sleep(CHECK_DELAY); + Thread.sleep(_checkDelay); } catch (InterruptedException ie) { // nop } } From fe575a38aa8088ce73fd83ed221257f0eba3f2c5 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 22:59:35 +0000 Subject: [PATCH 44/67] fix up DH like YK --- .../net/i2p/crypto/DHSessionKeyBuilder.java | 133 ++++++++++-------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java index b438f2b47..192bde062 100644 --- a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java +++ b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java @@ -13,8 +13,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -48,14 +47,15 @@ import net.i2p.util.RandomSource; * @author jrandom */ public class DHSessionKeyBuilder { - private static I2PAppContext _context = I2PAppContext.getGlobalContext(); - private final static Log _log = new Log(DHSessionKeyBuilder.class); - private static int MIN_NUM_BUILDERS = -1; - private static int MAX_NUM_BUILDERS = -1; - private static int CALC_DELAY = -1; - /* FIXME this should be final if you syncronize FIXME */ - private static volatile List _builders = new ArrayList(50); - private static Thread _precalcThread = null; + private static final I2PAppContext _context = I2PAppContext.getGlobalContext(); + private static final 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<DHSessionKeyBuilder> _builders; + private static final Thread _precalcThread; + + // the data of importance private BigInteger _myPrivateValue; private BigInteger _myPublicValue; private BigInteger _peerValue; @@ -65,17 +65,31 @@ public class DHSessionKeyBuilder { 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 = 5; - public final static int DEFAULT_DH_PRECALC_MAX = 50; - public final static int DEFAULT_DH_PRECALC_DELAY = 10000; + 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; + + /** check every 30 seconds whether we have less than the minimum */ + private static long _checkDelay = 30 * 1000; static { I2PAppContext ctx = _context; - ctx.statManager().createRateStat("crypto.dhGeneratePublicTime", "How long it takes to create x and X", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); - ctx.statManager().createRateStat("crypto.dhCalculateSessionTime", "How long it takes to create the session key", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 }); - MIN_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN); - MAX_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX); + _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(); + int factor = Math.min(4, (int) (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: " @@ -90,40 +104,33 @@ public class DHSessionKeyBuilder { /** * Construct a new DH key builder - * + * or pulls a prebuilt one from the queue. */ public DHSessionKeyBuilder() { - this(false); - DHSessionKeyBuilder builder = null; - synchronized (_builders) { - if (!_builders.isEmpty()) { - builder = (DHSessionKeyBuilder) _builders.remove(0); - if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing a builder. # left = " + _builders.size()); - } else { - if (_log.shouldLog(Log.WARN)) _log.warn("NO MORE BUILDERS! creating one now"); - } - } + _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; - _peerValue = builder._peerValue; - _sessionKey = builder._sessionKey; + // these two are still null after precalc + //_peerValue = builder._peerValue; + //_sessionKey = builder._sessionKey; _extraExchangedBytes = builder._extraExchangedBytes; } else { - _myPrivateValue = null; - _myPublicValue = null; - _peerValue = null; - _sessionKey = null; + 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(); } } - public DHSessionKeyBuilder(boolean usePool) { - _myPrivateValue = null; - _myPublicValue = null; - _peerValue = null; - _sessionKey = null; + /** + * Only for internal use + * @parameter usePool unused, just to make it different from other constructor + */ + private DHSessionKeyBuilder(boolean usePool) { _extraExchangedBytes = new ByteArray(); } @@ -189,18 +196,12 @@ public class DHSessionKeyBuilder { } private static final int getSize() { - synchronized (_builders) { return _builders.size(); - } } - private static final int addBuilder(DHSessionKeyBuilder builder) { - int sz = 0; - synchronized (_builders) { - _builders.add(builder); - sz = _builders.size(); - } - return sz; + /** @return true if successful, false if full */ + private static final boolean addBuilder(DHSessionKeyBuilder builder) { + return _builders.offer(builder); } /** @@ -210,7 +211,7 @@ public class DHSessionKeyBuilder { */ public BigInteger generateMyValue() { long start = System.currentTimeMillis(); - _myPrivateValue = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, RandomSource.getInstance()); + _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; @@ -314,6 +315,7 @@ public class DHSessionKeyBuilder { * If there aren't enough bytes (with all of them being consumed by the 32 byte key), * the SHA256 of the key itself is used. * + * @return non-null (but rv.getData() may be null) */ public ByteArray getExtraBytes() { return _extraExchangedBytes; @@ -406,6 +408,7 @@ public class DHSessionKeyBuilder { } */ +/****** public static void main(String args[]) { //if (true) { testValidation(); return; } @@ -419,7 +422,7 @@ public class DHSessionKeyBuilder { long negTime = 0; try { for (int i = 0; i < 5; i++) { - long startNeg = Clock.getInstance().now(); + long startNeg = System.currentTimeMillis(); DHSessionKeyBuilder builder1 = new DHSessionKeyBuilder(); DHSessionKeyBuilder builder2 = new DHSessionKeyBuilder(); BigInteger pub1 = builder1.getMyPublicValue(); @@ -428,7 +431,7 @@ public class DHSessionKeyBuilder { builder1.setPeerPublicValue(pub2); SessionKey key1 = builder1.getSessionKey(); SessionKey key2 = builder2.getSessionKey(); - long endNeg = Clock.getInstance().now(); + long endNeg = System.currentTimeMillis(); negTime += endNeg - startNeg; if (!key1.equals(key2)) @@ -458,10 +461,11 @@ public class DHSessionKeyBuilder { } catch (InterruptedException ie) { // nop } } +******/ private static class DHSessionKeyBuilderPrecalcRunner implements Runnable { - private int _minSize; - private int _maxSize; + private final int _minSize; + private final int _maxSize; private DHSessionKeyBuilderPrecalcRunner(int minSize, int maxSize) { _minSize = minSize; @@ -472,22 +476,28 @@ public class DHSessionKeyBuilder { while (true) { int curSize = 0; - long start = Clock.getInstance().now(); + long start = System.currentTimeMillis(); int startSize = getSize(); + // Adjust delay + if (startSize <= (_minSize * 2 / 3) && _checkDelay > 1000) + _checkDelay -= 1000; + else if (startSize > (_minSize * 3 / 2) && _checkDelay < 60*1000) + _checkDelay += 1000; curSize = startSize; - while (curSize < _minSize) { - while (curSize < _maxSize) { + if (curSize < _minSize) { + for (int i = curSize; i < _maxSize; i++) { long curStart = System.currentTimeMillis(); - curSize = addBuilder(precalc(curSize)); + if (!addBuilder(precalc())) + break; long curCalc = System.currentTimeMillis() - curStart; // for some relief... try { - Thread.sleep(CALC_DELAY + curCalc * 10); + Thread.sleep(CALC_DELAY + (curCalc * 3)); } catch (InterruptedException ie) { // nop } } } - long end = Clock.getInstance().now(); + long end = System.currentTimeMillis(); int numCalc = curSize - startSize; if (numCalc > 0) { if (_log.shouldLog(Log.DEBUG)) @@ -496,16 +506,15 @@ public class DHSessionKeyBuilder { + (CALC_DELAY * numCalc) + "ms relief). now sleeping"); } try { - Thread.sleep(30 * 1000); + Thread.sleep(_checkDelay); } catch (InterruptedException ie) { // nop } } } - private DHSessionKeyBuilder precalc(int i) { + private static DHSessionKeyBuilder precalc() { DHSessionKeyBuilder builder = new DHSessionKeyBuilder(false); builder.getMyPublicValue(); - //_log.debug("Precalc " + i + " complete"); return builder; } } From df3fc6e05db9e8b4bb5026cc54f84dfebeaf7612 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 23:19:34 +0000 Subject: [PATCH 45/67] Random cleanup --- .../net/i2p/util/BufferedRandomSource.java | 2 ++ .../src/net/i2p/util/PooledRandomSource.java | 2 ++ core/java/src/net/i2p/util/RandomSource.java | 36 ++++++------------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/core/java/src/net/i2p/util/BufferedRandomSource.java b/core/java/src/net/i2p/util/BufferedRandomSource.java index b9760af91..56b33390b 100644 --- a/core/java/src/net/i2p/util/BufferedRandomSource.java +++ b/core/java/src/net/i2p/util/BufferedRandomSource.java @@ -204,6 +204,7 @@ public class BufferedRandomSource extends RandomSource { return super.nextGaussian(); } +/***** public static void main(String args[]) { for (int i = 0; i < 16; i++) test(); @@ -232,4 +233,5 @@ public class BufferedRandomSource extends RandomSource { } return buf.toString(); } +*****/ } diff --git a/core/java/src/net/i2p/util/PooledRandomSource.java b/core/java/src/net/i2p/util/PooledRandomSource.java index e0beb46f8..71a929e7a 100644 --- a/core/java/src/net/i2p/util/PooledRandomSource.java +++ b/core/java/src/net/i2p/util/PooledRandomSource.java @@ -196,6 +196,7 @@ public class PooledRandomSource extends RandomSource { return prng.harvester(); } +/***** public static void main(String args[]) { //PooledRandomSource prng = new PooledRandomSource(I2PAppContext.getGlobalContext()); long start = System.currentTimeMillis(); @@ -214,4 +215,5 @@ public class PooledRandomSource extends RandomSource { System.out.println("Written to random.file: create took " + (created-start) + ", generate took " + (done-created)); prng.saveSeed(); } +*****/ } diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index 100ec47d7..c37c581dc 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -72,46 +72,30 @@ public class RandomSource extends SecureRandom implements EntropyHarvester { /** * override as synchronized, for those JVMs that don't always pull via * nextBytes (cough ibm) - */ + @Override public boolean nextBoolean() { return super.nextBoolean(); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public void nextBytes(byte buf[]) { super.nextBytes(buf); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public double nextDouble() { return super.nextDouble(); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public float nextFloat() { return super.nextFloat(); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public double nextGaussian() { return super.nextGaussian(); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public int nextInt() { return super.nextInt(); } - /** - * override as synchronized, for those JVMs that don't always pull via - * nextBytes (cough ibm) - */ + @Override public long nextLong() { return super.nextLong(); } - +*****/ + + /** */ public EntropyHarvester harvester() { return _entropyHarvester; } public void feedEntropy(String source, long data, int bitoffset, int bits) { From 0bd75f0e564c853b8cbb5f63be563910dc93de25 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 15 Dec 2010 23:35:50 +0000 Subject: [PATCH 46/67] comment out mains --- core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java | 2 ++ core/java/src/gnu/crypto/prng/FortunaStandalone.java | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java index c37ada8bc..849090f45 100644 --- a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java +++ b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java @@ -168,6 +168,7 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl //System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime); } +/***** public static void main(String args[]) { try { AsyncFortunaStandalone rand = new AsyncFortunaStandalone(null); // Will cause NPEs above; fix this if you want to test! Sorry... @@ -195,4 +196,5 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl } catch (Exception e) { e.printStackTrace(); } try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {} } +*****/ } diff --git a/core/java/src/gnu/crypto/prng/FortunaStandalone.java b/core/java/src/gnu/crypto/prng/FortunaStandalone.java index 0ba10c6ab..909e59ee2 100644 --- a/core/java/src/gnu/crypto/prng/FortunaStandalone.java +++ b/core/java/src/gnu/crypto/prng/FortunaStandalone.java @@ -351,6 +351,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl } } +/***** public static void main(String args[]) { byte in[] = new byte[16]; byte out[] = new byte[16]; @@ -379,7 +380,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl long after = System.currentTimeMillis(); System.out.println("encrypting 4MB took " + (after-beforeAll)); } catch (Exception e) { e.printStackTrace(); } - /* +****/ /* FortunaStandalone f = new FortunaStandalone(); java.util.HashMap props = new java.util.HashMap(); byte initSeed[] = new byte[1234]; @@ -394,5 +395,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl long time = System.currentTimeMillis() - before; System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps"); */ +/***** } +*****/ } From 03f58d1886bd645715ac5f7dd35ab27befaf4080 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 17 Dec 2010 21:48:04 +0000 Subject: [PATCH 47/67] shorten thread name --- apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java b/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java index fa29fbb05..52099d1d2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java +++ b/apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java @@ -137,7 +137,7 @@ public class ConnectionAcceptor implements Runnable } } } else { - Thread t = new I2PAppThread(new Handler(socket), "Connection-" + socket); + Thread t = new I2PAppThread(new Handler(socket), "I2PSnark incoming connection"); t.start(); } } From 60e57ec29edd19f601dc0936ff68ab4203b526a3 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 17 Dec 2010 21:49:34 +0000 Subject: [PATCH 48/67] add some types --- apps/i2psnark/java/src/org/klomp/snark/Message.java | 8 +++++++- apps/i2psnark/java/src/org/klomp/snark/Peer.java | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Message.java b/apps/i2psnark/java/src/org/klomp/snark/Message.java index cdde79a18..a9d1e23f2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Message.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Message.java @@ -39,7 +39,13 @@ class Message final static byte REQUEST = 6; final static byte PIECE = 7; final static byte CANCEL = 8; - final static byte EXTENSION = 20; + final static byte PORT = 9; // DHT (BEP 5) + final static byte SUGGEST = 13; // Fast (BEP 6) + final static byte HAVE_ALL = 14; // Fast (BEP 6) + final static byte HAVE_NONE = 15; // Fast (BEP 6) + final static byte REJECT = 16; // Fast (BEP 6) + final static byte ALLOWED_FAST = 17; // Fast (BEP 6) + final static byte EXTENSION = 20; // BEP 10 // Not all fields are used for every message. // KEEP_ALIVE doesn't have a real wire representation diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index d921f12e8..ae053b8c8 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -63,6 +63,7 @@ public class Peer implements Comparable // bytes per bt spec: 0011223344556677 static final long OPTION_EXTENSION = 0x0000000000100000l; static final long OPTION_FAST = 0x0000000000000004l; + static final long OPTION_DHT = 0x0000000000000001l; private long options; /** From d699eaaec9cc3f5a308ca33b0ae7b20dd3f25ed1 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 17 Dec 2010 21:50:14 +0000 Subject: [PATCH 49/67] change map class --- apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java index c1733cb13..986e45643 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java +++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java @@ -157,7 +157,7 @@ public class BEValue * values. This operation only succeeds when the BEValue is actually * a Map, otherwise it will throw a InvalidBEncodingException. */ - public Map<BEValue, BEValue> getMap() throws InvalidBEncodingException + public Map<String, BEValue> getMap() throws InvalidBEncodingException { try { From 7c8e5c6d66764cfe88432fbc5c772374059d4127 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 17 Dec 2010 22:04:57 +0000 Subject: [PATCH 50/67] retrofit SimpleDataStructure over SHA1Hash --- core/java/src/net/i2p/crypto/SHA1Hash.java | 46 ++++++---------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SHA1Hash.java b/core/java/src/net/i2p/crypto/SHA1Hash.java index acbb68d4b..c9a0f783d 100644 --- a/core/java/src/net/i2p/crypto/SHA1Hash.java +++ b/core/java/src/net/i2p/crypto/SHA1Hash.java @@ -14,8 +14,7 @@ import java.io.InputStream; import java.io.OutputStream; import net.i2p.data.DataFormatException; -import net.i2p.data.DataHelper; -import net.i2p.data.DataStructureImpl; +import net.i2p.data.SimpleDataStructure; /** * Because DSAEngine was abusing Hash for 20-byte hashes @@ -23,44 +22,31 @@ import net.i2p.data.DataStructureImpl; * @since 0.8.1 * @author zzz */ -public class SHA1Hash extends DataStructureImpl { - private byte[] _data; +public class SHA1Hash extends SimpleDataStructure { private int _cachedHashCode; public final static int HASH_LENGTH = SHA1.HASH_LENGTH; /** @throws IllegalArgumentException if data is not 20 bytes (null is ok) */ public SHA1Hash(byte data[]) { - setData(data); + super(data); } - public byte[] getData() { - return _data; + public int length() { + return HASH_LENGTH; } /** @throws IllegalArgumentException if data is not 20 bytes (null is ok) */ + @Override public void setData(byte[] data) { - // FIXME DSAEngine uses a SHA-1 "Hash" as parameters and return values! - if (data != null && data.length != HASH_LENGTH) - throw new IllegalArgumentException("Hash must be 20 bytes"); - _data = data; - _cachedHashCode = calcHashCode(); - } - - /** @throws IOException always */ - public void readBytes(InputStream in) throws DataFormatException, IOException { - throw new IOException("unimplemented"); - } - - /** @throws IOException always */ - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - throw new IOException("unimplemented"); + super.setData(data); + _cachedHashCode = super.hashCode(); } @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof SHA1Hash)) return false; - return DataHelper.eq(_data, ((SHA1Hash) obj)._data); + public void readBytes(InputStream in) throws DataFormatException, IOException { + super.readBytes(in); + _cachedHashCode = super.hashCode(); } /** a Hash is a hash, so just use the first 4 bytes for speed */ @@ -68,14 +54,4 @@ public class SHA1Hash extends DataStructureImpl { public int hashCode() { return _cachedHashCode; } - - /** a Hash is a hash, so just use the first 4 bytes for speed */ - private int calcHashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } } From 91f1ece753f46acb70904b91b02e1a2cb8b31ad4 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 19 Dec 2010 15:13:54 +0000 Subject: [PATCH 51/67] adjust runner count based on max mem --- router/java/src/net/i2p/router/JobQueue.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index 42ddadf5a..f56b25f9e 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -58,7 +58,16 @@ public class JobQueue { private final Object _jobLock; /** how many when we go parallel */ - private static final int RUNNERS = 4; + private static final int RUNNERS; + static { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory < 64*1024*1024) + RUNNERS = 3; + else if (maxMemory < 256*1024*1024) + RUNNERS = 4; + else + RUNNERS = 5; + } /** default max # job queue runners operating */ private final static int DEFAULT_MAX_RUNNERS = 1; From 63c6613261dc52e9c77171f7191e65bf8ec88d72 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 24 Dec 2010 16:52:23 +0000 Subject: [PATCH 52/67] * configclients.jsp: - Add form for I2CP options - Fix HTML errors --- .../i2p/router/web/ConfigClientsHandler.java | 40 +++++++- .../i2p/router/web/ConfigClientsHelper.java | 94 +++++++++++++++++-- apps/routerconsole/jsp/configclients.jsp | 46 ++++++++- .../themes/console/classic/console.css | 4 +- .../resources/themes/console/dark/console.css | 6 +- .../themes/console/light/console.css | 4 +- .../themes/console/midnight/console.css | 6 +- 7 files changed, 181 insertions(+), 19 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java index 91ac9726f..9a5d6bbf9 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.router.startup.ClientAppConfig; import net.i2p.router.startup.LoadClientAppsJob; @@ -35,6 +36,10 @@ public class ConfigClientsHandler extends FormHandler { saveClientChanges(); return; } + if (_action.equals(_("Save Interface Configuration"))) { + saveInterfaceChanges(); + return; + } if (_action.equals(_("Save WebApp Configuration"))) { saveWebAppChanges(); return; @@ -193,7 +198,7 @@ public class ConfigClientsHandler extends FormHandler { String[] arr = (String[]) _settings.get(key); if (arr == null) return null; - return arr[0]; + return arr[0].trim(); } private void startClient(int i) { @@ -337,4 +342,37 @@ public class ConfigClientsHandler extends FormHandler { _log.error("Error starting plugin " + app, e); } } + + /** + * Handle interface form + * @since 0.8.3 + */ + private void saveInterfaceChanges() { + String port = getJettyString("port"); + if (port != null) + _context.router().setConfigSetting(ClientManagerFacadeImpl.PROP_CLIENT_PORT, port); + String intfc = getJettyString("interface"); + if (intfc != null) + _context.router().setConfigSetting(ClientManagerFacadeImpl.PROP_CLIENT_HOST, intfc); + String user = getJettyString("user"); + if (user != null) + _context.router().setConfigSetting(ConfigClientsHelper.PROP_USER, user); + String pw = getJettyString("pw"); + if (pw != null) + _context.router().setConfigSetting(ConfigClientsHelper.PROP_PW, pw); + String mode = getJettyString("mode"); + boolean disabled = "0".equals(mode); + boolean ssl = "2".equals(mode); + _context.router().setConfigSetting(ConfigClientsHelper.PROP_DISABLE_EXTERNAL, + Boolean.toString(disabled)); + _context.router().setConfigSetting(ConfigClientsHelper.PROP_ENABLE_SSL, + Boolean.toString(ssl)); + _context.router().setConfigSetting(ConfigClientsHelper.PROP_AUTH, + Boolean.toString((_settings.get("auth") != null))); + boolean all = "0.0.0.0".equals(intfc) || "0:0:0:0:0:0:0:0".equals(intfc) || + "::".equals(intfc); + _context.router().setConfigSetting(ConfigClientsHelper.BIND_ALL_INTERFACES, Boolean.toString(all)); + _context.router().saveConfig(); + addFormNotice(_("Interface configuration saved successfully - restart required to take effect.")); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 049b5a02a..7cd2c8cb7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -1,6 +1,9 @@ package net.i2p.router.web; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -8,13 +11,90 @@ import java.util.Properties; import java.util.Set; import java.util.TreeSet; +import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.router.startup.ClientAppConfig; +import net.i2p.router.transport.Addresses; public class ConfigClientsHelper extends HelperBase { private String _edit; + /** from ClientListenerRunner */ + public static final String BIND_ALL_INTERFACES = "i2cp.tcp.bindAllInterfaces"; + /** from ClientManager */ + public static final String PROP_DISABLE_EXTERNAL = "i2cp.disableInterface"; + public static final String PROP_ENABLE_SSL = "i2cp.SSL"; + /** from ClientMessageEventListener */ + public static final String PROP_AUTH = "i2cp.auth"; + public static final String PROP_USER = "i2cp.username"; + public static final String PROP_PW = "i2cp.password"; + public ConfigClientsHelper() {} - + + /** @since 0.8.3 */ + public String getPort() { + return _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT, + Integer.toString(ClientManagerFacadeImpl.DEFAULT_PORT)); + } + + /** @since 0.8.3 */ + public String getUser() { + return _context.getProperty(PROP_USER, ""); + } + + /** @since 0.8.3 */ + public String getPw() { + return _context.getProperty(PROP_PW, ""); + } + + /** @since 0.8.3 */ + public String i2cpModeChecked(int mode) { + boolean disabled = _context.getBooleanProperty(PROP_DISABLE_EXTERNAL); + boolean ssl = _context.getBooleanProperty(PROP_ENABLE_SSL); + if ((mode == 0 && disabled) || + (mode == 1 && (!disabled) && (!ssl)) || + (mode == 2 && (!disabled) && ssl)) + return "checked=\"true\""; + return ""; + } + + /** @since 0.8.3 */ + public String getAuth() { + boolean enabled = _context.getBooleanProperty(PROP_AUTH); + if (enabled) + return "checked=\"true\""; + return ""; + } + + /** @since 0.8.3 */ + public String[] intfcAddresses() { + String[] addrs = Addresses.getAllAddresses(); + List<String> aList = new ArrayList(); + aList.addAll(Arrays.asList(addrs)); + boolean ipv6 = false; + for (String a : aList) { + if (a.indexOf(':') >= 0) { + ipv6 = true; + break; + } + } + if (!aList.contains("0.0.0.0")) + aList.add("0.0.0.0"); + if (ipv6 && !aList.contains("0:0:0:0:0:0:0:0")) + aList.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form + Collections.sort(aList); + return aList.toArray(addrs); + } + + /** @since 0.8.3 */ + public boolean isIFSelected(String addr) { + boolean bindAll = _context.getBooleanProperty(BIND_ALL_INTERFACES); + if (bindAll && addr.equals("0.0.0.0") || addr.equals("::")) + return true; + String host = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST, + ClientManagerFacadeImpl.DEFAULT_HOST); + return (host.equals(addr)); + } + public void setEdit(String edit) { if (edit == null) return; @@ -92,9 +172,9 @@ public class ConfigClientsHelper extends HelperBase { continue; StringBuilder desc = new StringBuilder(256); desc.append("<table border=\"0\">") - .append("<tr><td><b>").append(_("Version")).append("<td>").append(stripHTML(appProps, "version")) + .append("<tr><td><b>").append(_("Version")).append("</b></td><td>").append(stripHTML(appProps, "version")) .append("<tr><td><b>") - .append(_("Signed by")).append("<td>"); + .append(_("Signed by")).append("</b></td><td>"); String s = stripHTML(appProps, "signer"); if (s != null) { if (s.indexOf("@") > 0) @@ -111,13 +191,13 @@ public class ConfigClientsHelper extends HelperBase { if (ms > 0) { String date = (new SimpleDateFormat("yyyy-MM-dd HH:mm")).format(new Date(ms)); desc.append("<tr><td><b>") - .append(_("Date")).append("<td>").append(date); + .append(_("Date")).append("</b></td><td>").append(date); } } s = stripHTML(appProps, "author"); if (s != null) { desc.append("<tr><td><b>") - .append(_("Author")).append("<td>"); + .append(_("Author")).append("</b></td><td>"); if (s.indexOf("@") > 0) desc.append("<a href=\"mailto:").append(s).append("\">").append(s).append("</a>"); else @@ -128,12 +208,12 @@ public class ConfigClientsHelper extends HelperBase { s = stripHTML(appProps, "description"); if (s != null) { desc.append("<tr><td><b>") - .append(_("Description")).append("<td>").append(s); + .append(_("Description")).append("</b></td><td>").append(s); } s = stripHTML(appProps, "license"); if (s != null) { desc.append("<tr><td><b>") - .append(_("License")).append("<td>").append(s); + .append(_("License")).append("</b></td><td>").append(s); } s = stripHTML(appProps, "websiteURL"); if (s != null) { diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp index 4f633c894..a4d670295 100644 --- a/apps/routerconsole/jsp/configclients.jsp +++ b/apps/routerconsole/jsp/configclients.jsp @@ -47,7 +47,51 @@ button span.hide{ <input type="submit" name="edit" value="<%=intl._("Add Client")%>" /> <% } %> <input type="submit" name="action" value="<%=intl._("Save Client Configuration")%>" /> -</div></div><h3><a name="webapp"></a><%=intl._("WebApp Configuration")%></h3><p> +</div></div> + +<h3><a name="i2cp"></a><%=intl._("Advanced Client Interface Configuration")%></h3><p> +<b><%=intl._("External I2CP (I2P Client Protocol) Interface Configuration")%></b><br> +<input type="radio" class="optbox" name="mode" value="1" <%=clientshelper.i2cpModeChecked(1) %> > +<%=intl._("Enabled without SSL")%><br> +<input type="radio" class="optbox" name="mode" value="2" <%=clientshelper.i2cpModeChecked(2) %> > +<%=intl._("Enabled with SSL required")%><br> +<input type="radio" class="optbox" name="mode" value="0" <%=clientshelper.i2cpModeChecked(0) %> > +<%=intl._("Disabled - Clients outside this Java process may not connect")%><br> +<%=intl._("I2CP Port")%>: +<input name="port" type="text" size="5" maxlength="5" value="<jsp:getProperty name="clientshelper" property="port" />" ><br> +<%=intl._("I2CP Interface")%>: +<select name="interface"> +<% + String[] ips = clientshelper.intfcAddresses(); + for (int i = 0; i < ips.length; i++) { + out.print("<option value=\""); + out.print(ips[i]); + out.print('\"'); + if (clientshelper.isIFSelected(ips[i])) + out.print(" selected=\"selected\""); + out.print('>'); + out.print(ips[i]); + out.print("</option>\n"); + } +%> +</select><br> +<b><%=intl._("Authorization")%></b><br> +<input type="checkbox" class="optbox" name="auth" value="true" <jsp:getProperty name="clientshelper" property="auth" /> > +<%=intl._("Requre username and password")%><br> +<%=intl._("Username")%>: +<input name="user" type="text" value="<jsp:getProperty name="clientshelper" property="user" />" ><br> +<%=intl._("Password")%>: +<input name="pw" type="password" value="<jsp:getProperty name="clientshelper" property="pw" />" ><br> +</p><p><b><%=intl._("The default settings will work for most people.")%></b> +<%=intl._("Any changes made here must also be configured in the external client.")%> +<%=intl._("Many clients do not support SSL or authorization.")%> +<i><%=intl._("All changes require restart to take effect.")%></i> +</p><hr><div class="formaction"> +<input type="submit" name="foo" value="<%=intl._("Cancel")%>" /> +<input type="submit" name="action" value="<%=intl._("Save Interface Configuration")%>" /> +</div> + +<h3><a name="webapp"></a><%=intl._("WebApp Configuration")%></h3><p> <%=intl._("The Java web applications listed below are started by the webConsole client and run in the same JVM as the router. They are usually web applications accessible through the router console. They may be complete applications (e.g. i2psnark),front-ends to another client or application which must be separately enabled (e.g. susidns, i2ptunnel), or have no web interface at all (e.g. addressbook).")%> </p><p> <%=intl._("A web app may also be disabled by removing the .war file from the webapps directory; however the .war file and web app will reappear when you update your router to a newer version, so disabling the web app here is the preferred method.")%> diff --git a/installer/resources/themes/console/classic/console.css b/installer/resources/themes/console/classic/console.css index 3aa0c1424..2c2768053 100644 --- a/installer/resources/themes/console/classic/console.css +++ b/installer/resources/themes/console/classic/console.css @@ -714,7 +714,7 @@ input { vertical-align: middle; } -input[type=text] { +input[type=text], input[type=password] { margin: 3px 5px 3px 5px; vertical-align: middle; } @@ -910,4 +910,4 @@ div.footnote hr{ margin-top: -8px; margin-bottom: -5px; margin-right: 5px; -} \ No newline at end of file +} diff --git a/installer/resources/themes/console/dark/console.css b/installer/resources/themes/console/dark/console.css index c2a16245c..827002d53 100644 --- a/installer/resources/themes/console/dark/console.css +++ b/installer/resources/themes/console/dark/console.css @@ -843,7 +843,7 @@ input:active { color: #EE9; } -input[type=text] { +input[type=text], input[type=password] { background: #000; color: #EE9; margin: 5px 10px; @@ -859,7 +859,7 @@ input[type=text] { box-shadow: inset 1px 1px 1px 0px #000; } -input[type=text]:active, input[type=text]:hover { +input[type=text]:active, input[type=text]:hover, input[type=password]:active, input[type=password]:hover { background: #000; } @@ -1058,4 +1058,4 @@ div.footnote hr{ margin-top: -5px; margin-bottom: -5px; margin-right: 5px; -} \ No newline at end of file +} diff --git a/installer/resources/themes/console/light/console.css b/installer/resources/themes/console/light/console.css index 94dc14f3f..67cef2734 100644 --- a/installer/resources/themes/console/light/console.css +++ b/installer/resources/themes/console/light/console.css @@ -911,7 +911,7 @@ input:active { -moz-box-shadow: inset 0px 0px 0px 1px #f60; } -input[type=text] { +input[type=text], input[type=password] { background: #ffe; color: #001; margin: 5px 10px 5px 10px; @@ -1166,4 +1166,4 @@ div.footnote hr{ margin-top: 0px; margin-bottom: -18px; margin-right: 5px; -} \ No newline at end of file +} diff --git a/installer/resources/themes/console/midnight/console.css b/installer/resources/themes/console/midnight/console.css index 996259149..372364b74 100644 --- a/installer/resources/themes/console/midnight/console.css +++ b/installer/resources/themes/console/midnight/console.css @@ -750,7 +750,7 @@ input { vertical-align: middle; } -input[type=text] { +input[type=text], input[type=password] { margin: 3px 5px 3px 5px; vertical-align: middle; } @@ -760,7 +760,7 @@ select { vertical-align: middle; } -input[type=text], select { +input[type=text], input[type=password] select { background: #001; color: #eef; border: 1px solid #99f; @@ -959,4 +959,4 @@ div.footnote hr{ margin-top: -5px; margin-bottom: -10px; margin-right: 5px; -} \ No newline at end of file +} From 82e344055bf758491bd85f61381829d208e220e4 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 24 Dec 2010 17:39:31 +0000 Subject: [PATCH 53/67] * Certificate: Use a single static null cert in place of tens of thousands of them --- core/java/src/net/i2p/data/Certificate.java | 90 ++++++++++++++++++- core/java/src/net/i2p/data/KeysAndCert.java | 5 +- .../src/net/i2p/data/i2np/GarlicClove.java | 5 +- .../OutboundClientMessageJobHelper.java | 8 +- .../OutboundClientMessageOneShotJob.java | 2 +- .../networkdb/kademlia/MessageWrapper.java | 2 +- 6 files changed, 100 insertions(+), 12 deletions(-) diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java index 855d474ca..798c29680 100644 --- a/core/java/src/net/i2p/data/Certificate.java +++ b/core/java/src/net/i2p/data/Certificate.java @@ -26,8 +26,10 @@ import java.io.OutputStream; * @author jrandom */ public class Certificate extends DataStructureImpl { - private int _type; - private byte[] _payload; + public final static Certificate NULL_CERT = new NullCert(); + + protected int _type; + protected byte[] _payload; /** Specifies a null certificate type with no payload */ public final static int CERTIFICATE_TYPE_NULL = 0; @@ -41,6 +43,25 @@ public class Certificate extends DataStructureImpl { /** Contains multiple certs */ public final static int CERTIFICATE_TYPE_MULTIPLE = 4; + /** + * If null cert, return immutable static instance, else create new + * @since 0.8.3 + */ + public static Certificate create(InputStream in) throws DataFormatException, IOException { + int type = (int) DataHelper.readLong(in, 1); + int length = (int) DataHelper.readLong(in, 2); + if (type == 0 && length == 0) + return NULL_CERT; + // from here down roughly the same as readBytes() below + if (length == 0) + return new Certificate(type, null); + byte[] payload = new byte[length]; + int read = DataHelper.read(in, payload); + if (read != length) + throw new DataFormatException("Not enough bytes for the payload (read: " + read + " length: " + length + ')'); + return new Certificate(type, payload); + } + public Certificate() { } @@ -140,10 +161,12 @@ public class Certificate extends DataStructureImpl { Certificate cert = (Certificate) object; return _type == cert.getCertificateType() && DataHelper.eq(_payload, cert.getPayload()); } + @Override public int hashCode() { return _type + DataHelper.hashCode(_payload); } + @Override public String toString() { StringBuilder buf = new StringBuilder(64); @@ -177,4 +200,67 @@ public class Certificate extends DataStructureImpl { buf.append("]"); return buf.toString(); } + + /** + * An immutable null certificate. + * @since 0.8.3 + */ + private static final class NullCert extends Certificate { + private static final int NULL_LENGTH = 1 + 2; + private static final byte[] NULL_DATA = new byte[NULL_LENGTH]; + + public NullCert() { + // zero already + //_type = CERTIFICATE_TYPE_NULL; + } + + /** @throws RuntimeException always */ + @Override + public void setCertificateType(int type) { + throw new RuntimeException("Data already set"); + } + + /** @throws RuntimeException always */ + @Override + public void setPayload(byte[] payload) { + throw new RuntimeException("Data already set"); + } + + /** @throws RuntimeException always */ + @Override + public void readBytes(InputStream in) throws DataFormatException, IOException { + throw new RuntimeException("Data already set"); + } + + /** Overridden for efficiency */ + @Override + public void writeBytes(OutputStream out) throws IOException { + out.write(NULL_DATA); + } + + /** Overridden for efficiency */ + @Override + public int writeBytes(byte target[], int offset) { + System.arraycopy(NULL_DATA, 0, target, offset, NULL_LENGTH); + return offset + NULL_LENGTH; + } + + /** @throws RuntimeException always */ + @Override + public int readBytes(byte source[], int offset) throws DataFormatException { + throw new RuntimeException("Data already set"); + } + + /** Overridden for efficiency */ + @Override + public int size() { + return NULL_LENGTH; + } + + /** Overridden for efficiency */ + @Override + public int hashCode() { + return 99999; + } + } } diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index 046d428a3..d27d4bcf4 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -65,8 +65,9 @@ public class KeysAndCert extends DataStructureImpl { _publicKey.readBytes(in); _signingKey = new SigningPublicKey(); _signingKey.readBytes(in); - _certificate = new Certificate(); - _certificate.readBytes(in); + //_certificate = new Certificate(); + //_certificate.readBytes(in); + _certificate = Certificate.create(in); __calculatedHash = null; } diff --git a/router/java/src/net/i2p/data/i2np/GarlicClove.java b/router/java/src/net/i2p/data/i2np/GarlicClove.java index 1380dfd60..1067fb640 100644 --- a/router/java/src/net/i2p/data/i2np/GarlicClove.java +++ b/router/java/src/net/i2p/data/i2np/GarlicClove.java @@ -68,8 +68,9 @@ public class GarlicClove extends DataStructureImpl { _expiration = DataHelper.readDate(in); if (_log.shouldLog(Log.DEBUG)) _log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration); - _certificate = new Certificate(); - _certificate.readBytes(in); + //_certificate = new Certificate(); + //_certificate.readBytes(in); + _certificate = Certificate.create(in); if (_log.shouldLog(Log.DEBUG)) _log.debug("Read cert: " + _certificate); } diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java index 9d045c510..9025fd22b 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java @@ -115,7 +115,7 @@ class OutboundClientMessageJobHelper { instructions.setRouter(null); instructions.setTunnelId(null); - config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + config.setCertificate(Certificate.NULL_CERT); config.setDeliveryInstructions(instructions); config.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); config.setExpiration(expiration); // +2*Router.CLOCK_FUDGE_FACTOR); @@ -165,7 +165,7 @@ class OutboundClientMessageJobHelper { if (log.shouldLog(Log.DEBUG)) log.debug("Delivery status message key: " + replyToken + " arrival: " + msg.getArrival()); - ackClove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + ackClove.setCertificate(Certificate.NULL_CERT); ackClove.setDeliveryInstructions(ackInstructions); ackClove.setExpiration(expiration); ackClove.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); @@ -196,7 +196,7 @@ class OutboundClientMessageJobHelper { instructions.setDelaySeconds(0); instructions.setEncrypted(false); - clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + clove.setCertificate(Certificate.NULL_CERT); clove.setDeliveryInstructions(instructions); clove.setExpiration(expiration); clove.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); @@ -222,7 +222,7 @@ class OutboundClientMessageJobHelper { instructions.setDelaySeconds(0); instructions.setEncrypted(false); - clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + clove.setCertificate(Certificate.NULL_CERT); clove.setDeliveryInstructions(instructions); clove.setExpiration(expiration); clove.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 45622d117..ce1f36159 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -872,7 +872,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { instructions.setDelaySeconds(0); instructions.setEncrypted(false); - clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + clove.setCertificate(Certificate.NULL_CERT); clove.setDeliveryInstructions(instructions); clove.setExpiration(OVERALL_TIMEOUT_MS_DEFAULT+getContext().clock().now()); clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java index 1f521455e..b53269e52 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java @@ -46,7 +46,7 @@ class MessageWrapper { instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); PayloadGarlicConfig payload = new PayloadGarlicConfig(); - payload.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + payload.setCertificate(Certificate.NULL_CERT); payload.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE)); payload.setPayload(m); payload.setRecipient(to); From ff0c168d65ca3b27b421574dea22f77de27aeca1 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:29:55 +0000 Subject: [PATCH 54/67] * Certificate: Fix fatal null cert error --- core/java/src/net/i2p/data/Certificate.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java index 798c29680..d7ceabaeb 100644 --- a/core/java/src/net/i2p/data/Certificate.java +++ b/core/java/src/net/i2p/data/Certificate.java @@ -111,8 +111,10 @@ public class Certificate extends DataStructureImpl { DataHelper.writeLong(out, 2, 0L); } } - - + + /** + * @return the written length (NOT the new offset) + */ public int writeBytes(byte target[], int offset) { int cur = offset; DataHelper.toLong(target, cur, 1, _type); @@ -242,7 +244,7 @@ public class Certificate extends DataStructureImpl { @Override public int writeBytes(byte target[], int offset) { System.arraycopy(NULL_DATA, 0, target, offset, NULL_LENGTH); - return offset + NULL_LENGTH; + return NULL_LENGTH; } /** @throws RuntimeException always */ From c54b40288bf6c795ade7bb376c2d75c795f62b6e Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:31:01 +0000 Subject: [PATCH 55/67] * Addresses: Add listing capability for internal interfaces --- .../net/i2p/router/transport/Addresses.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/Addresses.java b/router/java/src/net/i2p/router/transport/Addresses.java index dab8cfc6d..5200e1173 100644 --- a/router/java/src/net/i2p/router/transport/Addresses.java +++ b/router/java/src/net/i2p/router/transport/Addresses.java @@ -31,17 +31,40 @@ public class Addresses { } /** - * @return an array of all addresses, excluding + * @return a sorted array of all addresses, excluding * IPv6, local, broadcast, multicast, etc. */ public static String[] getAddresses() { + return getAddresses(false); + } + + /** + * @return a sorted array of all addresses, excluding + * only link local and multicast + * @since 0.8.3 + */ + public static String[] getAllAddresses() { + return getAddresses(true); + } + + /** + * @return a sorted array of all addresses + * @param whether to exclude IPV6 and local + * @return an array of all addresses + * @since 0.8.3 + */ + public static String[] getAddresses(boolean all) { Set<String> rv = new HashSet(4); try { InetAddress localhost = InetAddress.getLocalHost(); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); if (allMyIps != null) { - for (int i = 0; i < allMyIps.length; i++) - add(rv, allMyIps[i]); + for (int i = 0; i < allMyIps.length; i++) { + if (all) + addAll(rv, allMyIps[i]); + else + add(rv, allMyIps[i]); + } } } catch (UnknownHostException e) {} @@ -50,7 +73,10 @@ public class Addresses { NetworkInterface ifc = ifcs.nextElement(); for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { InetAddress addr = addrs.nextElement(); - add(rv, addr); + if (all) + addAll(rv, addr); + else + add(rv, addr); } } } catch (SocketException e) {} @@ -79,8 +105,16 @@ public class Addresses { set.add(ip); } + private static void addAll(Set<String> set, InetAddress ia) { + if (ia.isLinkLocalAddress() || + ia.isMulticastAddress()) + return; + String ip = ia.getHostAddress(); + set.add(ip); + } + public static void main(String[] args) { - String[] a = getAddresses(); + String[] a = getAddresses(true); for (String s : a) System.err.println("Address: " + s); } From 66f348450845196517e55377b2ae36d9374e8a95 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:32:17 +0000 Subject: [PATCH 56/67] * i2psnark: Backport TrackerClient NPE fix --- apps/i2psnark/java/src/org/klomp/snark/Peer.java | 6 +++--- apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index ae053b8c8..73ba93280 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -78,7 +78,7 @@ public class Peer implements Comparable this.my_id = my_id; this.metainfo = metainfo; _id = ++__id; - //_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating")); + //_log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating")); } /** @@ -102,7 +102,7 @@ public class Peer implements Comparable this.peerID = new PeerID(id, sock.getPeerDestination()); _id = ++__id; if (_log.shouldLog(Log.DEBUG)) - _log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id)); + _log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating " + _id)); } /** @@ -198,7 +198,7 @@ public class Peer implements Comparable throw new IllegalStateException("Peer already started"); if (_log.shouldLog(Log.DEBUG)) - _log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting")); + _log.debug("Running connection to " + peerID.toString(), new Exception("connecting")); try { // Do we need to handshake? diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index a1a3f4cc2..89815035b 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -276,7 +276,7 @@ public class TrackerClient extends I2PAppThread // only delay if we actually make an attempt to add peer if(coordinator.addPeer(cur)) { int delay = DELAY_MUL; - delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10; + delay *= r.nextInt(10); delay += DELAY_MIN; sleptTime += delay; try { Thread.sleep(delay); } catch (InterruptedException ie) {} From 7ee7cbf660f87b56f5507f34fe452bf96740f88f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:35:54 +0000 Subject: [PATCH 57/67] * Stats: Change stat.full default.to false (saves ~ 1MB) --- .../java/src/net/i2p/router/web/ConfigStatsHelper.java | 2 +- core/java/src/net/i2p/stat/StatManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigStatsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigStatsHelper.java index 07c0f3e0f..254a20728 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigStatsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigStatsHelper.java @@ -139,6 +139,6 @@ public class ConfigStatsHelper extends HelperBase { public boolean getCurrentCanBeGraphed() { return _currentCanBeGraphed; } public String getExplicitFilter() { return _filter; } public boolean getIsFull() { - return _context.getBooleanPropertyDefaultTrue(StatManager.PROP_STAT_FULL); + return _context.getBooleanProperty(StatManager.PROP_STAT_FULL); } } diff --git a/core/java/src/net/i2p/stat/StatManager.java b/core/java/src/net/i2p/stat/StatManager.java index 5af634067..531138d1a 100644 --- a/core/java/src/net/i2p/stat/StatManager.java +++ b/core/java/src/net/i2p/stat/StatManager.java @@ -206,7 +206,7 @@ public class StatManager { * @return true if the stat should be ignored. */ public boolean ignoreStat(String statName) { - if (_context.getBooleanPropertyDefaultTrue(PROP_STAT_FULL)) + if (_context.getBooleanProperty(PROP_STAT_FULL)) return false; String required = _context.getProperty(PROP_STAT_REQUIRED, DEFAULT_STAT_REQUIRED); String req[] = required.split(","); From 34973371acff2a88d8e3a6ec343a2160da6aaa00 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:37:51 +0000 Subject: [PATCH 58/67] * Update: - Change the UpdateHandler to try all sources in a loop, rather than one, so the user need not retry manually - For each source, fetch the first 56 bytes and check the version before downloading the whole thing, so we need not wait for every host to have the latest before updating the news. --- .../src/net/i2p/router/web/UpdateHandler.java | 130 ++++++++++++++---- 1 file changed, 100 insertions(+), 30 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java index 9d9370191..174704ba6 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java @@ -1,8 +1,11 @@ package net.i2p.router.web; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.StringTokenizer; @@ -15,10 +18,12 @@ import net.i2p.router.RouterVersion; import net.i2p.util.EepGet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.PartialEepGet; +import net.i2p.util.VersionComparator; /** - * <p>Handles the request to update the router by firing off an - * {@link net.i2p.util.EepGet} call to download the latest signed update file + * <p>Handles the request to update the router by firing one or more + * {@link net.i2p.util.EepGet} calls to download the latest signed update file * and displaying the status to anyone who asks. * </p> * <p>After the download completes the signed update file is verified with @@ -125,6 +130,11 @@ public class UpdateHandler { protected boolean done; protected EepGet _get; protected final DecimalFormat _pct = new DecimalFormat("0.0%"); + /** tells the listeners what mode we are in */ + private boolean _isPartial; + /** set by the listeners on completion */ + private boolean _isNewer; + private ByteArrayOutputStream _baos; public UpdateRunner() { _isRunning = false; @@ -141,39 +151,88 @@ public class UpdateHandler { System.setProperty(PROP_UPDATE_IN_PROGRESS, "false"); _isRunning = false; } + + /** + * Loop through the entire list of update URLs. + * For each one, first get the version from the first 56 bytes and see if + * it is newer than what we are running now. + * If it is, get the whole thing. + */ protected void update() { - updateStatus("<b>" + _("Updating") + "</b>"); // TODO: // Do a PartialEepGet on the selected URL, check for version we expect, // and loop if it isn't what we want. // This will allow us to do a release without waiting for the last host to install the update. // Alternative: In bytesTransferred(), Check the data in the output file after // we've received at least 56 bytes. Need a cancel() method in EepGet ? - String updateURL = selectUpdateURL(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Selected update URL: " + updateURL); + boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue(); String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST); int proxyPort = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT); - try { - if (shouldProxy) - // 40 retries!! - _get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, updateURL, false); - else - _get = new EepGet(_context, 1, _updateFile, updateURL, false); - _get.addStatusListener(UpdateRunner.this); - _get.fetch(); - } catch (Throwable t) { - _log.error("Error updating", t); + + List<String> urls = getUpdateURLs(); + if (urls.isEmpty()) { + // not likely, don't bother translating + updateStatus("<b>Update source list is empty, cannot download update</b>"); + _log.log(Log.CRIT, "Update source list is empty - cannot download update"); + return; + } + + if (shouldProxy) + _baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES); + for (String updateURL : urls) { + updateStatus("<b>" + _("Updating from {0}", linkify(updateURL)) + "</b>"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Selected update URL: " + updateURL); + + // Check the first 56 bytes for the version + if (shouldProxy) { + _isPartial = true; + _isNewer = false; + _baos.reset(); + try { + // no retries + _get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, updateURL, TrustedUpdate.HEADER_BYTES); + _get.addStatusListener(UpdateRunner.this); + _get.fetch(); + } catch (Throwable t) { + _isNewer = false; + } + _isPartial = false; + if (!_isNewer) + continue; + } + + // Now get the whole thing + try { + if (shouldProxy) + // 40 retries!! + _get = new EepGet(_context, proxyHost, proxyPort, 40, _updateFile, updateURL, false); + else + _get = new EepGet(_context, 1, _updateFile, updateURL, false); + _get.addStatusListener(UpdateRunner.this); + _get.fetch(); + } catch (Throwable t) { + _log.error("Error updating", t); + } + if (this.done) + break; } } + // EepGet Listeners below. + // We use the same for both the partial and the full EepGet, + // with a couple of adjustments depending on which mode. + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + _isNewer = false; if (_log.shouldLog(Log.DEBUG)) _log.debug("Attempt failed on " + url, cause); // ignored } public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { + if (_isPartial) + return; StringBuilder buf = new StringBuilder(64); buf.append("<b>").append(_("Updating")).append("</b> "); double pct = ((double)alreadyTransferred + (double)currentWrite) / @@ -186,6 +245,19 @@ public class UpdateHandler { updateStatus(buf.toString()); } public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) { + if (_isPartial) { + // Compare version with what we have now + String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray())); + boolean newer = (new VersionComparator()).compare(newVersion, RouterVersion.VERSION) > 0; + if (!newer) { + updateStatus("<b>" + _("No new version found at {0}", linkify(url)) + "</b>"); + if (_log.shouldLog(Log.WARN)) + _log.warn("Found old version \"" + newVersion + "\" at " + url); + } + _isNewer = newer; + return; + } + // Process the .sud/.su2 file updateStatus("<b>" + _("Update downloaded") + "</b>"); TrustedUpdate up = new TrustedUpdate(_context); File f = new File(_updateFile); @@ -223,15 +295,16 @@ public class UpdateHandler { } } else { _log.log(Log.CRIT, err + " from " + url); - updateStatus("<b>" + err + ' ' + _("from {0}", url) + " </b>"); + updateStatus("<b>" + err + ' ' + _("from {0}", linkify(url)) + " </b>"); } } public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _isNewer = false; // don't display bytesTransferred as it is meaningless - _log.log(Log.CRIT, "Update from " + url + " did not download completely (" + + _log.error("Update from " + url + " did not download completely (" + bytesRemaining + " remaining after " + currentAttempt + " tries)"); - updateStatus("<b>" + _("Transfer failed") + "</b>"); + updateStatus("<b>" + _("Transfer failed from {0}", linkify(url)) + "</b>"); } public void headerReceived(String url, int attemptNum, String key, String val) {} public void attempting(String url) {} @@ -242,27 +315,24 @@ public class UpdateHandler { _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); } - private String selectUpdateURL() { + private List<String> getUpdateURLs() { String URLs = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL); StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n"); - List URLList = new ArrayList(); + List<String> URLList = new ArrayList(); while (tok.hasMoreTokens()) URLList.add(tok.nextToken().trim()); - int size = URLList.size(); - //_log.log(Log.DEBUG, "Picking update source from " + size + " candidates."); - if (size <= 0) { - _log.log(Log.CRIT, "Update source list is empty - cannot download update"); - return null; - } - int index = I2PAppContext.getGlobalContext().random().nextInt(size); - _log.log(Log.DEBUG, "Picked update source " + index + "."); - return (String) URLList.get(index); + Collections.shuffle(URLList, _context.random()); + return URLList; } protected void updateStatus(String s) { _status = s; } + protected static String linkify(String url) { + return "<a target=\"_blank\" href=\"" + url + "\"/>" + url + "</a>"; + } + /** translate a string */ protected String _(String s) { return Messages.getString(s, _context); From 824d5a0d369d6927eb9c48e654f0db353973825f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 12:42:44 +0000 Subject: [PATCH 59/67] javadocs --- core/java/src/net/i2p/data/DataHelper.java | 11 +++++++++++ core/java/src/net/i2p/data/Destination.java | 5 ++++- core/java/src/net/i2p/data/Payload.java | 4 ++++ core/java/src/net/i2p/util/FortunaRandomSource.java | 4 +++- core/java/src/net/i2p/util/RandomSource.java | 13 +++++++++---- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 60ba86f1c..bd6bf260d 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -914,9 +914,20 @@ public class DataHelper { return c; } + /** + * This is different than InputStream.read(target), in that it + * does repeated reads until the full data is received. + */ public static int read(InputStream in, byte target[]) throws IOException { return read(in, target, 0, target.length); } + + /** + * This is different than InputStream.read(target, offset, length), in that it + * returns the new offset (== old offset + bytes read). + * It also does repeated reads until the full data is received. + * @return the new offset (== old offset + bytes read) + */ public static int read(InputStream in, byte target[], int offset, int length) throws IOException { int cur = offset; while (cur < length) { diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 41478275b..a8c176df6 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -28,7 +28,10 @@ public class Destination extends KeysAndCert { fromBase64(s); } - /** deprecated, used only by Packet.java in streaming */ + /** + * deprecated, used only by Packet.java in streaming + * @return the written length (NOT the new offset) + */ public int writeBytes(byte target[], int offset) { int cur = offset; System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES); diff --git a/core/java/src/net/i2p/data/Payload.java b/core/java/src/net/i2p/data/Payload.java index 0371ed73f..bdaac7d4c 100644 --- a/core/java/src/net/i2p/data/Payload.java +++ b/core/java/src/net/i2p/data/Payload.java @@ -93,6 +93,10 @@ public class Payload extends DataStructureImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("wrote payload: " + _encryptedData.length); } + + /** + * @return the written length (NOT the new offset) + */ public int writeBytes(byte target[], int offset) { if (_encryptedData == null) throw new IllegalStateException("Not yet encrypted. Please set the encrypted data"); DataHelper.toLong(target, offset, 4, _encryptedData.length); diff --git a/core/java/src/net/i2p/util/FortunaRandomSource.java b/core/java/src/net/i2p/util/FortunaRandomSource.java index 8f499935b..9061d3a57 100644 --- a/core/java/src/net/i2p/util/FortunaRandomSource.java +++ b/core/java/src/net/i2p/util/FortunaRandomSource.java @@ -23,7 +23,7 @@ import net.i2p.crypto.EntropyHarvester; * */ public class FortunaRandomSource extends RandomSource implements EntropyHarvester { - private AsyncFortunaStandalone _fortuna; + private final AsyncFortunaStandalone _fortuna; private double _nextGaussian; private boolean _haveNextGaussian; @@ -210,6 +210,7 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste _fortuna.addRandomBytes(data, offset, len); } +/***** public static void main(String args[]) { try { RandomSource rand = I2PAppContext.getGlobalContext().random(); @@ -231,4 +232,5 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste System.out.println("Compressed size of 1MB: " + compressed.length); } catch (Exception e) { e.printStackTrace(); } } +*****/ } diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index c37c581dc..c7c87239c 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -25,18 +25,23 @@ import net.i2p.data.Base64; * @author jrandom */ public class RandomSource extends SecureRandom implements EntropyHarvester { - private Log _log; - private EntropyHarvester _entropyHarvester; - protected I2PAppContext _context; + private final EntropyHarvester _entropyHarvester; + protected final I2PAppContext _context; public RandomSource(I2PAppContext context) { super(); _context = context; - _log = context.logManager().getLog(RandomSource.class); // when we replace to have hooks for fortuna (etc), replace with // a factory (or just a factory method) _entropyHarvester = this; } + + /** + * Singleton for whatever PRNG i2p uses. + * Same as I2PAppContext.getGlobalContext().random(); + * use context.random() if you have a context already. + * @return I2PAppContext.getGlobalContext().random() + */ public static RandomSource getInstance() { return I2PAppContext.getGlobalContext().random(); } From b3d1a76146654285a3ab9470e0f9c0543feb716f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 13:04:52 +0000 Subject: [PATCH 60/67] Prep for caching by making SimpleDataStructures immutable after data has been set. --- .../src/net/i2p/data/SimpleDataStructure.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/data/SimpleDataStructure.java b/core/java/src/net/i2p/data/SimpleDataStructure.java index 96c1585c5..4754c29ec 100644 --- a/core/java/src/net/i2p/data/SimpleDataStructure.java +++ b/core/java/src/net/i2p/data/SimpleDataStructure.java @@ -20,6 +20,12 @@ import net.i2p.crypto.SHA256Generator; * * Implemented in 0.8.2 and retrofitted over several of the classes in this package. * + * As of 0.8.3, SDS objects may be cached. An SDS may be instantiated with null data, + * and setData(null) is also OK. However, + * once non-null data is set, the data reference is immutable; + * subsequent attempts to set the data via setData(), readBytes(), + * or fromBase64() will throw a RuntimeException. + * * @since 0.8.2 * @author zzz */ @@ -57,14 +63,24 @@ public abstract class SimpleDataStructure extends DataStructureImpl { * Sets the data. * @param data of correct length, or null * @throws IllegalArgumentException if data is not the legal number of bytes (but null is ok) + * @throws RuntimeException if data already set. */ public void setData(byte[] data) { + if (_data != null) + throw new RuntimeException("Data already set"); if (data != null && data.length != _length) throw new IllegalArgumentException("Bad data length"); _data = data; } + /** + * Sets the data. + * @param data of correct length, or null + * @throws RuntimeException if data already set. + */ public void readBytes(InputStream in) throws DataFormatException, IOException { + if (_data != null) + throw new RuntimeException("Data already set"); _data = new byte[_length]; int read = read(in, _data); if (read != _length) throw new DataFormatException("Not enough bytes to read the data"); @@ -85,6 +101,7 @@ public abstract class SimpleDataStructure extends DataStructureImpl { /** * Sets the data. * @throws DataFormatException if decoded data is not the legal number of bytes or on decoding error + * @throws RuntimeException if data already set. */ @Override public void fromBase64(String data) throws DataFormatException { @@ -162,5 +179,4 @@ public abstract class SimpleDataStructure extends DataStructureImpl { if ((obj == null) || !(obj instanceof SimpleDataStructure)) return false; return DataHelper.eq(_data, ((SimpleDataStructure) obj)._data); } - } From cd0d062fca76ea8d4c460f4f36b2c99ca6cda7b4 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 13:17:12 +0000 Subject: [PATCH 61/67] Fix last piece length calculation for torrents > 2GB (ticket #361) --- apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java index 140d3cb46..ad2680045 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java @@ -291,7 +291,7 @@ public class MetaInfo if (piece >= 0 && piece < pieces -1) return piece_length; else if (piece == pieces -1) - return (int)(length - piece * piece_length); + return (int)(length - ((long)piece * piece_length)); else throw new IndexOutOfBoundsException("no piece: " + piece); } From 5d494ba89af89467db7d7997d3b92c8db62c4c2f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 13:48:45 +0000 Subject: [PATCH 62/67] Move Addresses class from router to core so we can use it in i2ptunnel --- .../java/src/net/i2p/router/web/ConfigClientsHelper.java | 2 +- .../java/src/net/i2p/router/web/ConfigNetHelper.java | 2 +- .../transport => core/java/src/net/i2p/util}/Addresses.java | 3 ++- router/java/src/net/i2p/router/transport/TransportManager.java | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) rename {router/java/src/net/i2p/router/transport => core/java/src/net/i2p/util}/Addresses.java (98%) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 7cd2c8cb7..4e25719a0 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -13,7 +13,7 @@ import java.util.TreeSet; import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.router.startup.ClientAppConfig; -import net.i2p.router.transport.Addresses; +import net.i2p.util.Addresses; public class ConfigClientsHelper extends HelperBase { private String _edit; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index 512b51593..392b3c06b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -4,11 +4,11 @@ import net.i2p.data.DataHelper; import net.i2p.data.RouterAddress; import net.i2p.router.CommSystemFacade; import net.i2p.router.Router; -import net.i2p.router.transport.Addresses; import net.i2p.router.transport.TransportManager; import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.time.Timestamper; +import net.i2p.util.Addresses; public class ConfigNetHelper extends HelperBase { public ConfigNetHelper() {} diff --git a/router/java/src/net/i2p/router/transport/Addresses.java b/core/java/src/net/i2p/util/Addresses.java similarity index 98% rename from router/java/src/net/i2p/router/transport/Addresses.java rename to core/java/src/net/i2p/util/Addresses.java index 5200e1173..b81d43c23 100644 --- a/router/java/src/net/i2p/router/transport/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -1,4 +1,4 @@ -package net.i2p.router.transport; +package net.i2p.util; /* * public domain @@ -18,6 +18,7 @@ import java.util.Set; /** * Get the local addresses * + * @since 0.8.3 moved to core * @author zzz */ public class Addresses { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index cf21af11d..a682840b1 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -32,6 +32,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.router.transport.ntcp.NTCPTransport; import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.util.Addresses; import net.i2p.util.Log; import net.i2p.util.Translate; From 49b11bb49e0d3569adc773a8e693b97d527ea4a3 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 15:07:59 +0000 Subject: [PATCH 63/67] refactor Addresses --- .../i2p/router/web/ConfigClientsHelper.java | 20 +--- .../net/i2p/router/web/ConfigNetHelper.java | 5 +- core/java/src/net/i2p/util/Addresses.java | 99 ++++++++++--------- 3 files changed, 57 insertions(+), 67 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 4e25719a0..4ce5c5b99 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -2,8 +2,6 @@ package net.i2p.router.web; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -67,22 +65,8 @@ public class ConfigClientsHelper extends HelperBase { /** @since 0.8.3 */ public String[] intfcAddresses() { - String[] addrs = Addresses.getAllAddresses(); - List<String> aList = new ArrayList(); - aList.addAll(Arrays.asList(addrs)); - boolean ipv6 = false; - for (String a : aList) { - if (a.indexOf(':') >= 0) { - ipv6 = true; - break; - } - } - if (!aList.contains("0.0.0.0")) - aList.add("0.0.0.0"); - if (ipv6 && !aList.contains("0:0:0:0:0:0:0:0")) - aList.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form - Collections.sort(aList); - return aList.toArray(addrs); + ArrayList<String> al = new ArrayList(Addresses.getAllAddresses()); + return al.toArray(new String[al.size()]); } /** @since 0.8.3 */ diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index 392b3c06b..9388d06d2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -1,5 +1,7 @@ package net.i2p.router.web; +import java.util.ArrayList; + import net.i2p.data.DataHelper; import net.i2p.data.RouterAddress; import net.i2p.router.CommSystemFacade; @@ -147,7 +149,8 @@ public class ConfigNetHelper extends HelperBase { } public String[] getAddresses() { - return Addresses.getAddresses(); + ArrayList<String> al = new ArrayList(Addresses.getAddresses()); + return al.toArray(new String[al.size()]); } public String getInboundRate() { diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index b81d43c23..3af5ea65a 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -9,10 +9,10 @@ import java.net.Inet4Address; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; -import java.util.Arrays; import java.util.Enumeration; -import java.util.HashSet; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; /** @@ -21,31 +21,31 @@ import java.util.Set; * @since 0.8.3 moved to core * @author zzz */ -public class Addresses { +public abstract class Addresses { /** @return the first non-local address it finds, or null */ public static String getAnyAddress() { - String[] a = getAddresses(); - if (a.length > 0) - return a[0]; + SortedSet<String> a = getAddresses(); + if (!a.isEmpty()) + return a.first(); return null; } /** - * @return a sorted array of all addresses, excluding + * @return a sorted set of all addresses, excluding * IPv6, local, broadcast, multicast, etc. */ - public static String[] getAddresses() { - return getAddresses(false); + public static SortedSet<String> getAddresses() { + return getAddresses(false, false); } /** - * @return a sorted array of all addresses, excluding + * @return a sorted set of all addresses, excluding * only link local and multicast * @since 0.8.3 */ - public static String[] getAllAddresses() { - return getAddresses(true); + public static SortedSet<String> getAllAddresses() { + return getAddresses(true, true); } /** @@ -54,17 +54,15 @@ public class Addresses { * @return an array of all addresses * @since 0.8.3 */ - public static String[] getAddresses(boolean all) { - Set<String> rv = new HashSet(4); + public static SortedSet<String> getAddresses(boolean includeLocal, boolean includeIPv6) { + SortedSet<String> rv = new TreeSet(); try { InetAddress localhost = InetAddress.getLocalHost(); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); if (allMyIps != null) { for (int i = 0; i < allMyIps.length; i++) { - if (all) - addAll(rv, allMyIps[i]); - else - add(rv, allMyIps[i]); + if (shouldInclude(allMyIps[i], includeLocal, includeIPv6)) + rv.add(allMyIps[i].getHostAddress()); } } } catch (UnknownHostException e) {} @@ -74,49 +72,54 @@ public class Addresses { NetworkInterface ifc = ifcs.nextElement(); for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { InetAddress addr = addrs.nextElement(); - if (all) - addAll(rv, addr); - else - add(rv, addr); + if (shouldInclude(addr, includeLocal, includeIPv6)) + rv.add(addr.getHostAddress()); } } } catch (SocketException e) {} - String[] rva = rv.toArray(new String[rv.size()]); - Arrays.sort(rva); - return rva; + if (includeLocal) + rv.add("0.0.0.0"); + if (includeLocal && includeIPv6) { + boolean ipv6 = false; + for (String a : rv) { + if (a.indexOf(':') >= 0) { + ipv6 = true; + break; + } + } + if (ipv6) + rv.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form + } + return rv; } - private static void add(Set<String> set, InetAddress ia) { - if (ia.isAnyLocalAddress() || - ia.isLinkLocalAddress() || - ia.isLoopbackAddress() || - ia.isMulticastAddress() || - ia.isSiteLocalAddress() || + private static boolean shouldInclude(InetAddress ia, boolean includeLocal, boolean includeIPv6) { + return + (!ia.isLinkLocalAddress()) && + (!ia.isMulticastAddress()) && + (includeLocal || + ((!ia.isAnyLocalAddress()) && + (!ia.isLoopbackAddress()) && + (!ia.isSiteLocalAddress()))) && // Hamachi 5/8 allocated to RIPE (30 November 2010) // Removed from TransportImpl.isPubliclyRoutable() // Check moved to here, for now, but will eventually need to // remove it from here also. - ia.getHostAddress().startsWith("5.") || - !(ia instanceof Inet4Address)) { -// System.err.println("Skipping: " + ia.getHostAddress()); - return; - } - String ip = ia.getHostAddress(); - set.add(ip); - } - - private static void addAll(Set<String> set, InetAddress ia) { - if (ia.isLinkLocalAddress() || - ia.isMulticastAddress()) - return; - String ip = ia.getHostAddress(); - set.add(ip); + (includeLocal || + (!ia.getHostAddress().startsWith("5."))) && + (includeIPv6 || + (ia instanceof Inet4Address)); } public static void main(String[] args) { - String[] a = getAddresses(true); + System.err.println("External Addresses:"); + Set<String> a = getAddresses(false, false); for (String s : a) - System.err.println("Address: " + s); + System.err.println(s); + System.err.println("All addresses:"); + a = getAddresses(true, true); + for (String s : a) + System.err.println(s); } } From 443abce6479608056f7a7c335fea3bf42fcee558 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 20:36:44 +0000 Subject: [PATCH 64/67] * I2CP: - Move BWLimits and DestLookup message support from I2PSimpleSession to I2PSessionImpl - Include the Hash in the DestReplyMessage on a failed lookup so the client may correlate replies - Add support for parallel lookups and BWLimits requests - Add support for specifying the timeout for DestLookups (can only be smaller than the router timeout for now) - Extend dest lookup router timeout from 10s to 15s --- .../src/org/klomp/snark/I2PSnarkUtil.java | 31 ++++- .../i2p/client/DestReplyMessageHandler.java | 11 +- .../client/I2PClientMessageHandlerMap.java | 6 + core/java/src/net/i2p/client/I2PSession.java | 14 ++- .../src/net/i2p/client/I2PSessionImpl.java | 108 +++++++++++++++++- .../src/net/i2p/client/I2PSimpleSession.java | 57 --------- .../src/net/i2p/client/naming/LookupDest.java | 7 ++ .../net/i2p/data/i2cp/DestReplyMessage.java | 55 +++++++-- 8 files changed, 211 insertions(+), 78 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 001928d47..b36c0fdcb 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -14,11 +14,13 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PSession; +import net.i2p.client.I2PSessionException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketEepGet; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketManagerFactory; +import net.i2p.data.Base32; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -316,21 +318,44 @@ public class I2PSnarkUtil { } } + private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5 + /** Base64 Hash or Hash.i2p or name.i2p using naming service */ Destination getDestination(String ip) { if (ip == null) return null; if (ip.endsWith(".i2p")) { if (ip.length() < 520) { // key + ".i2p" - Destination dest = _context.namingService().lookup(ip); - if (dest != null) - return dest; + if (_manager != null && ip.length() == BASE32_HASH_LENGTH + 8 && ip.endsWith(".b32.i2p")) { + // Use existing I2PSession for b32 lookups if we have it + // This is much more efficient than using the naming service + I2PSession sess = _manager.getSession(); + if (sess != null) { + byte[] b = Base32.decode(ip.substring(0, BASE32_HASH_LENGTH)); + if (b != null) { + Hash h = new Hash(b); + if (_log.shouldLog(Log.INFO)) + _log.info("Using existing session for lookup of " + ip); + try { + return sess.lookupDest(h); + } catch (I2PSessionException ise) { + } + } + } + } + if (_log.shouldLog(Log.INFO)) + _log.info("Using naming service for lookup of " + ip); + return _context.namingService().lookup(ip); } + if (_log.shouldLog(Log.INFO)) + _log.info("Creating Destination for " + ip); try { return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p } catch (DataFormatException dfe) { return null; } } else { + if (_log.shouldLog(Log.INFO)) + _log.info("Creating Destination for " + ip); try { return new Destination(ip); } catch (DataFormatException dfe) { diff --git a/core/java/src/net/i2p/client/DestReplyMessageHandler.java b/core/java/src/net/i2p/client/DestReplyMessageHandler.java index 573389cfb..8d5527d59 100644 --- a/core/java/src/net/i2p/client/DestReplyMessageHandler.java +++ b/core/java/src/net/i2p/client/DestReplyMessageHandler.java @@ -10,6 +10,9 @@ import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.util.Log; +import net.i2p.data.Destination; +import net.i2p.data.Hash; + /** * Handle I2CP dest replies from the router */ @@ -22,6 +25,12 @@ class DestReplyMessageHandler extends HandlerImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("Handle message " + message); DestReplyMessage msg = (DestReplyMessage) message; - ((I2PSimpleSession)session).destReceived(msg.getDestination()); + Destination d = msg.getDestination(); + if (d != null) + session.destReceived(d); + Hash h = msg.getHash(); + if (h != null) + session.destLookupFailed(h); + // else let it time out } } diff --git a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java index 6f0d95051..76350cbb2 100644 --- a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java +++ b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java @@ -10,6 +10,8 @@ package net.i2p.client; */ import net.i2p.I2PAppContext; +import net.i2p.data.i2cp.BandwidthLimitsMessage; +import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.DisconnectMessage; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.MessageStatusMessage; @@ -36,6 +38,8 @@ class I2PClientMessageHandlerMap { highest = Math.max(highest, MessagePayloadMessage.MESSAGE_TYPE); highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE); highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE); + highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE); + highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE); _handlers = new I2CPMessageHandler[highest+1]; _handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context); @@ -44,6 +48,8 @@ class I2PClientMessageHandlerMap { _handlers[MessagePayloadMessage.MESSAGE_TYPE] = new MessagePayloadMessageHandler(context); _handlers[MessageStatusMessage.MESSAGE_TYPE] = new MessageStatusMessageHandler(context); _handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context); + _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); + _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); } public I2CPMessageHandler getHandler(int messageTypeId) { diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java index 1998dad55..cd20cfc2d 100644 --- a/core/java/src/net/i2p/client/I2PSession.java +++ b/core/java/src/net/i2p/client/I2PSession.java @@ -138,13 +138,21 @@ public interface I2PSession { public SigningPrivateKey getPrivateKey(); /** - * Lookup up a Hash - * + * Lookup a Destination by Hash. + * Blocking. Waits a max of 10 seconds by default. */ public Destination lookupDest(Hash h) throws I2PSessionException; /** - * Get the current bandwidth limits + * Blocking. + * @param maxWait ms + * @since 0.8.3 + * @return null on failure + */ + public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException; + + /** + * Get the current bandwidth limits. Blocking. */ public int[] bandwidthLimits() throws I2PSessionException; diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 8090e0eae..e101ff725 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -15,7 +15,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; -import java.util.concurrent.ConcurrentHashMap; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -23,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; @@ -33,6 +34,8 @@ import net.i2p.data.PrivateKey; import net.i2p.data.SessionKey; import net.i2p.data.SessionTag; import net.i2p.data.SigningPrivateKey; +import net.i2p.data.i2cp.DestLookupMessage; +import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetDateMessage; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; @@ -95,6 +98,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa protected I2CPMessageProducer _producer; /** map of Long --> MessagePayloadMessage */ protected Map<Long, MessagePayloadMessage> _availableMessages; + + /** hashes of lookups we are waiting for */ + protected final LinkedBlockingQueue<LookupWaiter> _pendingLookups = new LinkedBlockingQueue(); + protected final Object _bwReceivedLock = new Object(); + protected int[] _bwLimits; protected I2PClientMessageHandlerMap _handlerMap; @@ -786,12 +794,104 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa return buf.toString(); } - public Destination lookupDest(Hash h) throws I2PSessionException { - return null; + /** called by the message handler */ + void destReceived(Destination d) { + Hash h = d.calculateHash(); + for (LookupWaiter w : _pendingLookups) { + if (w.hash.equals(h)) { + w.destination = d; + synchronized (w) { + w.notifyAll(); + } + } + } } + /** called by the message handler */ + void destLookupFailed(Hash h) { + for (LookupWaiter w : _pendingLookups) { + if (w.hash.equals(h)) { + synchronized (w) { + w.notifyAll(); + } + } + } + } + + /** called by the message handler */ + void bwReceived(int[] i) { + _bwLimits = i; + synchronized (_bwReceivedLock) { + _bwReceivedLock.notifyAll(); + } + } + + /** + * Simple object to wait for lookup replies + * @since 0.8.3 + */ + private static class LookupWaiter { + /** the request */ + public final Hash hash; + /** the reply */ + public Destination destination; + + public LookupWaiter(Hash h) { + this.hash = h; + } + } + + /** + * Blocking. Waits a max of 10 seconds by default. + * See lookupDest with maxWait parameter to change. + * Implemented in 0.8.3 in I2PSessionImpl; + * previously was available only in I2PSimpleSession. + * Multiple outstanding lookups are now allowed. + * @return null on failure + */ + public Destination lookupDest(Hash h) throws I2PSessionException { + return lookupDest(h, 10*1000); + } + + /** + * Blocking. + * @param maxWait ms + * @since 0.8.3 + * @return null on failure + */ + public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException { + if (_closed) + return null; + LookupWaiter waiter = new LookupWaiter(h); + _pendingLookups.offer(waiter); + sendMessage(new DestLookupMessage(h)); + try { + synchronized (waiter) { + waiter.wait(maxWait); + } + } catch (InterruptedException ie) {} + _pendingLookups.remove(waiter); + return waiter.destination; + } + + /** + * Blocking. Waits a max of 5 seconds. + * But shouldn't take long. + * Implemented in 0.8.3 in I2PSessionImpl; + * previously was available only in I2PSimpleSession. + * Multiple outstanding lookups are now allowed. + * @return null on failure + */ public int[] bandwidthLimits() throws I2PSessionException { - return null; + if (_closed) + return null; + sendMessage(new GetBandwidthLimitsMessage()); + try { + synchronized (_bwReceivedLock) { + _bwReceivedLock.wait(5*1000); + } + } catch (InterruptedException ie) {} + return _bwLimits; } protected void updateActivity() { diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index ed9ec5cc3..e984b6d30 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -33,12 +33,6 @@ import net.i2p.util.I2PAppThread; * @author zzz */ class I2PSimpleSession extends I2PSessionImpl2 { - private boolean _destReceived; - private /* FIXME final FIXME */ Object _destReceivedLock; - private Destination _destination; - private boolean _bwReceived; - private /* FIXME final FIXME */ Object _bwReceivedLock; - private int[] _bwLimits; /** * Create a new session for doing naming and bandwidth queries only. Do not create a destination. @@ -104,57 +98,6 @@ class I2PSimpleSession extends I2PSessionImpl2 { } } - /** called by the message handler */ - void destReceived(Destination d) { - _destReceived = true; - _destination = d; - synchronized (_destReceivedLock) { - _destReceivedLock.notifyAll(); - } - } - - void bwReceived(int[] i) { - _bwReceived = true; - _bwLimits = i; - synchronized (_bwReceivedLock) { - _bwReceivedLock.notifyAll(); - } - } - - @Override - public Destination lookupDest(Hash h) throws I2PSessionException { - if (_closed) - return null; - _destReceivedLock = new Object(); - sendMessage(new DestLookupMessage(h)); - for (int i = 0; i < 10 && !_destReceived; i++) { - try { - synchronized (_destReceivedLock) { - _destReceivedLock.wait(1000); - } - } catch (InterruptedException ie) {} - } - _destReceived = false; - return _destination; - } - - @Override - public int[] bandwidthLimits() throws I2PSessionException { - if (_closed) - return null; - _bwReceivedLock = new Object(); - sendMessage(new GetBandwidthLimitsMessage()); - for (int i = 0; i < 5 && !_bwReceived; i++) { - try { - synchronized (_bwReceivedLock) { - _bwReceivedLock.wait(1000); - } - } catch (InterruptedException ie) {} - } - _bwReceived = false; - return _bwLimits; - } - /** * Only map message handlers that we will use */ diff --git a/core/java/src/net/i2p/client/naming/LookupDest.java b/core/java/src/net/i2p/client/naming/LookupDest.java index c90b4a673..d131efade 100644 --- a/core/java/src/net/i2p/client/naming/LookupDest.java +++ b/core/java/src/net/i2p/client/naming/LookupDest.java @@ -22,6 +22,13 @@ import net.i2p.data.Hash; * * All calls are blocking and return null on failure. * Timeout is set to 10 seconds in I2PSimpleSession. + * + * As of 0.8.3, standard I2PSessions support lookups, + * including multiple lookups in parallel, and overriding + * the default timeout. + * Using an existing I2PSession is much more efficient and + * flexible than using this class. + * */ class LookupDest { diff --git a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java index d3b2df9e1..7aaba9c89 100644 --- a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java +++ b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java @@ -13,14 +13,18 @@ import java.io.InputStream; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.Hash; /** - * Response to DestLookupMessage - * + * Response to DestLookupMessage. + * As of 0.8.3, the response may include the hash from the request, indicating + * a failure for a specific request. + * Payload may be empty (failure), a Hash (failure), or a Destination. */ public class DestReplyMessage extends I2CPMessageImpl { public final static int MESSAGE_TYPE = 35; private Destination _dest; + private Hash _hash; public DestReplyMessage() { super(); @@ -30,23 +34,52 @@ public class DestReplyMessage extends I2CPMessageImpl { _dest = d; } + /** + * @param h non-null with non-null data + * @since 0.8.3 + */ + public DestReplyMessage(Hash h) { + _hash = h; + } + public Destination getDestination() { return _dest; } + /** + * @since 0.8.3 + */ + public Hash getHash() { + return _hash; + } + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { - try { - Destination d = new Destination(); - d.readBytes(in); - _dest = d; - } catch (DataFormatException dfe) { - _dest = null; // null dest allowed + if (size == 0) { + _dest = null; + _hash = null; + } else { + try { + if (size == Hash.HASH_LENGTH) { + Hash h = new Hash(); + h.readBytes(in); + _hash = h; + } else { + Destination d = new Destination(); + d.readBytes(in); + _dest = d; + } + } catch (DataFormatException dfe) { + _dest = null; + _hash = null; + } } } protected byte[] doWriteMessage() throws I2CPMessageException, IOException { - if (_dest == null) + if (_dest == null && _hash == null) return new byte[0]; // null response allowed + if (_dest == null && _hash != null) + return _hash.getData(); ByteArrayOutputStream os = new ByteArrayOutputStream(_dest.size()); try { _dest.writeBytes(os); @@ -65,7 +98,8 @@ public class DestReplyMessage extends I2CPMessageImpl { public boolean equals(Object object) { if ((object != null) && (object instanceof DestReplyMessage)) { DestReplyMessage msg = (DestReplyMessage) object; - return DataHelper.eq(getDestination(), msg.getDestination()); + return DataHelper.eq(getDestination(), msg.getDestination()) && + DataHelper.eq(getHash(), msg.getHash()); } return false; } @@ -75,6 +109,7 @@ public class DestReplyMessage extends I2CPMessageImpl { StringBuilder buf = new StringBuilder(); buf.append("[DestReplyMessage: "); buf.append("\n\tDestination: ").append(_dest); + buf.append("\n\tHash: ").append(_hash); buf.append("]"); return buf.toString(); } From cdcbc802482e2d257ab2567f25736cb1da68cb60 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 22:57:13 +0000 Subject: [PATCH 65/67] Addresses cleanup --- core/java/src/net/i2p/util/Addresses.java | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index 3af5ea65a..a9c8e6804 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -55,12 +55,18 @@ public abstract class Addresses { * @since 0.8.3 */ public static SortedSet<String> getAddresses(boolean includeLocal, boolean includeIPv6) { + boolean haveIPv4 = false; + boolean haveIPv6 = false; SortedSet<String> rv = new TreeSet(); try { InetAddress localhost = InetAddress.getLocalHost(); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); if (allMyIps != null) { for (int i = 0; i < allMyIps.length; i++) { + if (allMyIps[i] instanceof Inet4Address) + haveIPv4 = true; + else + haveIPv6 = true; if (shouldInclude(allMyIps[i], includeLocal, includeIPv6)) rv.add(allMyIps[i].getHostAddress()); } @@ -72,25 +78,20 @@ public abstract class Addresses { NetworkInterface ifc = ifcs.nextElement(); for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { InetAddress addr = addrs.nextElement(); + if (addr instanceof Inet4Address) + haveIPv4 = true; + else + haveIPv6 = true; if (shouldInclude(addr, includeLocal, includeIPv6)) rv.add(addr.getHostAddress()); } } } catch (SocketException e) {} - if (includeLocal) + if (includeLocal && haveIPv4) rv.add("0.0.0.0"); - if (includeLocal && includeIPv6) { - boolean ipv6 = false; - for (String a : rv) { - if (a.indexOf(':') >= 0) { - ipv6 = true; - break; - } - } - if (ipv6) - rv.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form - } + if (includeLocal && includeIPv6 && haveIPv6) + rv.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form return rv; } From fefcb6c2cbe9c727ef197e50272a87e43d294320 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 26 Dec 2010 23:35:27 +0000 Subject: [PATCH 66/67] prevent leak after exception --- core/java/src/net/i2p/client/I2PSessionImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index e101ff725..8b4389e47 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -864,13 +864,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa return null; LookupWaiter waiter = new LookupWaiter(h); _pendingLookups.offer(waiter); - sendMessage(new DestLookupMessage(h)); try { - synchronized (waiter) { - waiter.wait(maxWait); - } - } catch (InterruptedException ie) {} - _pendingLookups.remove(waiter); + sendMessage(new DestLookupMessage(h)); + try { + synchronized (waiter) { + waiter.wait(maxWait); + } + } catch (InterruptedException ie) {} + } finally { + _pendingLookups.remove(waiter); + } return waiter.destination; } From 6ee162002a2b1d8d97fd2d6b8ebad8827d1f714d Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 27 Dec 2010 15:56:19 +0000 Subject: [PATCH 67/67] fix console jetty temp file names when in ssl-only mode --- .../java/src/net/i2p/router/web/RouterConsoleRunner.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 870f162d4..f2acafdff 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -210,7 +210,7 @@ public class RouterConsoleRunner { _server.addListener(ssll); boundAddresses++; } catch (Exception e) { // probably no exceptions at this point - System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + " for SSL: " + e); + System.err.println("Unable to bind routerconsole to " + host + " port " + sslPort + " for SSL: " + e); } } } else { @@ -224,7 +224,8 @@ public class RouterConsoleRunner { } _server.setRootWebApp(ROUTERCONSOLE); WebApplicationContext wac = _server.addWebApplication("/", _webAppsDir + ROUTERCONSOLE + ".war"); - File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" + _listenPort); + File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" + + (_listenPort != null ? _listenPort : _sslListenPort)); tmpdir.mkdir(); wac.setTempDirectory(tmpdir); baseHandler = new LocaleWebAppHandler(I2PAppContext.getGlobalContext()); @@ -239,7 +240,8 @@ public class RouterConsoleRunner { String enabled = props.getProperty(PREFIX + appName + ENABLED); if (! "false".equals(enabled)) { String path = new File(dir, fileNames[i]).getCanonicalPath(); - tmpdir = new SecureDirectory(workDir, appName + "-" + _listenPort); + tmpdir = new SecureDirectory(workDir, appName + "-" + + (_listenPort != null ? _listenPort : _sslListenPort)); WebAppStarter.addWebApp(I2PAppContext.getGlobalContext(), _server, appName, path, tmpdir); if (enabled == null) {