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(); } } 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/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/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); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index d921f12e8..73ba93280 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; /** @@ -77,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")); } /** @@ -101,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)); } /** @@ -197,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/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index d53ddde5d..034f74ae7 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/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index e3cc8ae9f..89815035b 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; @@ -274,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) {} 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 getMap() throws InvalidBEncodingException + public Map getMap() throws InvalidBEncodingException { try { 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/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index c06a7231c..44c163532 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 e48bae1a9..a98551b8c 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; @@ -34,9 +41,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; @@ -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 { @@ -109,9 +105,9 @@ 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); + Thread t = new I2PAppThread(this, "Client " + tunnel.listenHost + ':' + localPort); listenerReady = false; t.start(); open = true; @@ -125,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"); @@ -135,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 { @@ -163,6 +158,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) { @@ -210,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); @@ -224,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 * @@ -321,6 +284,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 +343,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; @@ -537,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); @@ -566,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) { @@ -586,9 +549,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna notifyAll(); } } - synchronized (_waitingSockets) { - _waitingSockets.notifyAll(); - } } /** @@ -598,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; @@ -625,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); @@ -688,7 +636,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna //l.log("Client closed."); } - synchronized (_waitingSockets) { _waitingSockets.notifyAll(); } return true; } @@ -696,40 +643,10 @@ 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); } } - /** - * 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() 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..a9b1a8287 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,15 +893,15 @@ 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); } } - 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/"; @@ -940,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("
_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 db5e9101a..d4efaa8ea 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -31,7 +31,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"; @@ -40,6 +40,20 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { private static final String[] CLIENT_SKIPHEADERS = {HASH_HEADER, DEST64_HEADER, DEST32_HEADER}; private static final String SERVER_HEADER = "Server"; private static final String[] SERVER_SKIPHEADERS = {SERVER_HEADER}; + private static final long HEADER_TIMEOUT = 60*1000; + + 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); @@ -73,8 +87,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(); @@ -124,19 +139,30 @@ 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 { 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 + // 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(); @@ -150,25 +176,24 @@ 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 [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]"); } 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() { if (_log.shouldLog(Log.INFO)) _log.info("Compressed requestor running"); @@ -183,7 +208,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(); @@ -214,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 @@ -233,14 +258,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"); @@ -277,16 +307,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; @@ -352,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() + "]"); // FIXME we probably don't need or want this in the outgoing direction int trimmed = 0; @@ -409,8 +439,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 + "]"); } } } 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..4537389bd 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -61,9 +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 Log _log = new Log(I2PTunnelIRCServer.class); - + private static final long HEADER_TIMEOUT = 60*1000; /** * @throws IllegalArgumentException if the I2PTunnel does not contain @@ -108,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); @@ -126,12 +125,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(); @@ -181,8 +180,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 +213,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..5427130bc 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.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; @@ -30,8 +36,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; @@ -48,12 +53,17 @@ 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 = 65; + /** min number of threads */ + private static final int MIN_HANDLERS = 0; + /** how long to wait before dropping an idle thread */ + private static final long HANDLER_KEEPALIVE_MS = 30*1000; protected I2PTunnelTask task = null; protected boolean bidir = false; @@ -67,8 +77,8 @@ 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,7 +89,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); - SetUsePool(tunnel); + _log = tunnel.getContext().logManager().getLog(getClass()); FileInputStream fis = null; try { fis = new FileInputStream(privkey); @@ -99,19 +109,10 @@ 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); - SetUsePool(tunnel); + _log = tunnel.getContext().logManager().getLog(getClass()); 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; @@ -143,6 +144,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) { @@ -199,8 +210,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(); } @@ -236,7 +246,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()); @@ -259,67 +269,106 @@ 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 executor pool. + * Then, do the accept() loop, and either + * hands each I2P socket to the executor or runs it in-line. + */ public void run() { - 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(); - 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 + I2PServerSocket i2pS_S = sockMgr.getServerSocket(); + 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) { + 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); + } + } 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(). + */ + private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { + public CustomThreadPoolExecutor(int max, String name) { + super(MIN_HANDLERS, max, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS, + new SynchronousQueue(), 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. there are still lots - * (and lots) of wasted threads within the I2PTunnelRunner, but its a start - * + * 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); } } @@ -335,20 +384,21 @@ 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(); - } 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) + "]"); } } 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<String, List<String>> 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); 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..87beb689c 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; @@ -171,14 +172,23 @@ 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) { 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"); } @@ -234,6 +244,35 @@ 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"); + } + + 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) { @@ -270,7 +309,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 +325,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 2035f4627..bb1a339c2 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; @@ -537,11 +538,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); } @@ -643,9 +644,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"); } @@ -671,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 { @@ -712,6 +728,50 @@ 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"; + protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams"; + + 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()); + } + + 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) { @@ -904,16 +964,20 @@ 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", - "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" + "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_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)); @@ -929,12 +993,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 +1086,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..f2b427542 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 %> @@ -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>): diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 773d323a2..b0f870fb7 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"> @@ -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"> @@ -359,19 +359,64 @@ </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: 4em;" 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"> + <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="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 /> </div> 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/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..4ce5c5b99 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,7 @@ package net.i2p.router.web; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -8,13 +9,76 @@ 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.util.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() { + ArrayList<String> al = new ArrayList(Addresses.getAllAddresses()); + return al.toArray(new String[al.size()]); + } + + /** @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 +156,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 +175,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 +192,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/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index 512b51593..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,14 +1,16 @@ package net.i2p.router.web; +import java.util.ArrayList; + 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() {} @@ -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/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/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 72acbb540..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,14 +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) { - 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/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(); } 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/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 68533f7ef..f2acafdff 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -4,37 +4,53 @@ 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; 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 +58,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 +87,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 below + _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,27 +163,69 @@ 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 " + sslPort + " 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); 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()); @@ -131,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) { @@ -201,6 +311,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", KeyStore.getDefaultType(), + "-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/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); 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/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 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 314ff4e44..03abafdda 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java @@ -114,7 +114,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { /** * - * @return connected I2PSocket + * @return connected I2PSocket OR NULL * @throws net.i2p.I2PException * @throws java.net.SocketTimeoutException */ diff --git a/build.xml b/build.xml index 102bde2af..7f6ee43b4 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" /> @@ -239,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/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"); */ +/***** } +*****/ } diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index b49779640..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,7 +22,9 @@ 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; import net.i2p.util.Clock; import net.i2p.util.ConcurrentHashSet; @@ -363,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 { @@ -843,4 +848,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/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/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/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 ea75f73ef..8b4389e47 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,14 +34,18 @@ 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; import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.SessionId; -import net.i2p.util.I2PThread; -import net.i2p.util.InternalSocket; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.InternalClientManager; +import net.i2p.internal.QueuedI2CPMessageReader; +import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -66,9 +71,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; @@ -79,6 +84,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; @@ -86,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; @@ -122,6 +139,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) { @@ -172,19 +192,24 @@ 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()) { + // 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 { + _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 - 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"); @@ -272,10 +297,6 @@ 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); - notifier.start(); if ( (_options != null) && (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(_options.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT))) ) { @@ -285,17 +306,32 @@ 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 { + 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) { + _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); + 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"); @@ -435,6 +471,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; @@ -497,8 +537,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()); @@ -515,7 +556,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) { @@ -567,9 +610,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 +629,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 { @@ -631,7 +678,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa _log.warn("Error destroying the session", ipe); } } - _availabilityNotifier.stopNotifying(); + // SimpleSession does not initialize + if (_availabilityNotifier != null) + _availabilityNotifier.stopNotifying(); _closed = true; _closing = false; closeSocket(); @@ -649,6 +698,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; @@ -666,7 +719,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")); @@ -733,21 +788,113 @@ 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(); } - 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); + try { + sendMessage(new DestLookupMessage(h)); + try { + synchronized (waiter) { + waiter.wait(maxWait); + } + } catch (InterruptedException ie) {} + } finally { + _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 f4bc3e812..e984b6d30 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -19,8 +19,10 @@ 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.util.I2PThread; -import net.i2p.util.InternalSocket; +import net.i2p.internal.I2CPMessageQueue; +import net.i2p.internal.InternalClientManager; +import net.i2p.internal.QueuedI2CPMessageReader; +import net.i2p.util.I2PAppThread; /** * Create a new session for doing naming and bandwidth queries only. Do not create a Destination. @@ -31,12 +33,6 @@ import net.i2p.util.InternalSocket; * @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. @@ -44,12 +40,12 @@ 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); _closed = true; _closing = false; - _availabilityNotifier = new AvailabilityNotifier(); if (options == null) options = System.getProperties(); loadConfig(options); @@ -65,23 +61,32 @@ class I2PSimpleSession extends I2PSessionImpl2 { @Override public void connect() throws I2PSessionException { _closed = false; - _availabilityNotifier.stopNotifying(); - I2PThread notifier = new I2PThread(_availabilityNotifier); - notifier.setName("Simple Notifier"); - notifier.setDaemon(true); - 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 { + 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); + _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); + // we do not receive payload messages, so we do not need an AvailabilityNotifier _reader.startReading(); } catch (UnknownHostException uhe) { @@ -93,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/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/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; } } 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 { } } } +****/ } 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; - } } 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 } } diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java index 855d474ca..d7ceabaeb 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() { } @@ -90,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); @@ -140,10 +163,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 +202,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 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/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/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/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/data/SimpleDataStructure.java b/core/java/src/net/i2p/data/SimpleDataStructure.java index 46fac745d..0017e567c 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); } - } 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(); } diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java index 057892b65..39633881e 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; @@ -175,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/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..dda1301ee --- /dev/null +++ b/core/java/src/net/i2p/internal/PoisonI2CPMessage.java @@ -0,0 +1,56 @@ +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() { + return "[PoisonMessage]"; + } +} 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..d713b678d --- /dev/null +++ b/core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java @@ -0,0 +1,62 @@ +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 cancelRunner() { + super.cancelRunner(); + _readerThread.interrupt(); + } + + @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) {} + } + // ??? unused + if (_stayAlive && !_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 @@ +<html><body> +<p> +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. +</p> +</body></html> 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(","); diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java new file mode 100644 index 000000000..a9c8e6804 --- /dev/null +++ b/core/java/src/net/i2p/util/Addresses.java @@ -0,0 +1,126 @@ +package net.i2p.util; + +/* + * public domain + */ + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + + +/** + * Get the local addresses + * + * @since 0.8.3 moved to core + * @author zzz + */ +public abstract class Addresses { + + /** @return the first non-local address it finds, or null */ + public static String getAnyAddress() { + SortedSet<String> a = getAddresses(); + if (!a.isEmpty()) + return a.first(); + return null; + } + + /** + * @return a sorted set of all addresses, excluding + * IPv6, local, broadcast, multicast, etc. + */ + public static SortedSet<String> getAddresses() { + return getAddresses(false, false); + } + + /** + * @return a sorted set of all addresses, excluding + * only link local and multicast + * @since 0.8.3 + */ + public static SortedSet<String> getAllAddresses() { + return getAddresses(true, 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 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()); + } + } + } catch (UnknownHostException e) {} + + try { + for(Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces(); ifcs.hasMoreElements();) { + 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 && haveIPv4) + rv.add("0.0.0.0"); + 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; + } + + 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. + (includeLocal || + (!ia.getHostAddress().startsWith("5."))) && + (includeIPv6 || + (ia instanceof Inet4Address)); + } + + public static void main(String[] args) { + System.err.println("External Addresses:"); + Set<String> a = getAddresses(false, false); + for (String s : a) + System.err.println(s); + System.err.println("All addresses:"); + a = getAddresses(true, true); + for (String s : a) + System.err.println(s); + } +} 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/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index b56f197ab..b5bb48a43 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,37 +231,79 @@ public class FileUtil { } /** - * This won't work right if one of the two options in unpack() is commented out. + * 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; } 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 +420,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 +450,7 @@ public class FileUtil { } } + /***** private static void testRmdir() { File t = new File("rmdirTest/test/subdir/blah"); boolean created = t.mkdirs(); @@ -417,4 +461,5 @@ public class FileUtil { else System.out.println("PASS: rmdirTest deleted"); } + *****/ } 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/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(); } 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..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(); } @@ -72,46 +77,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) { 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/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java index ee7d36e99..f764debe9 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(); } @@ -65,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); } @@ -90,7 +101,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/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 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> diff --git a/installer/resources/themes/console/classic/console.css b/installer/resources/themes/console/classic/console.css index 4fa629cb7..0d301a879 100644 --- a/installer/resources/themes/console/classic/console.css +++ b/installer/resources/themes/console/classic/console.css @@ -718,7 +718,7 @@ input { vertical-align: middle; } -input[type=text] { +input[type=text], input[type=password] { margin: 3px 5px 3px 5px; vertical-align: middle; } diff --git a/installer/resources/themes/console/dark/console.css b/installer/resources/themes/console/dark/console.css index 84134f1f2..8921b88c9 100644 --- a/installer/resources/themes/console/dark/console.css +++ b/installer/resources/themes/console/dark/console.css @@ -1,1083 +1,1083 @@ -/* I2P Theme: Camo aka Dark */ -/* Description: Military Grade. */ -/* Comment: Thanks to Florian Kuhlmann for the hatface images. [ http://www.flickr.com/photos/floriankuhlmann/] -/* Author: dr|z3d */ - -body { - margin: 5px 0px 0 0px; - padding: 0; - text-align: center; - background: #010 url('images/camotile.png') center bottom; - color: #EE9; - font: 9pt/130% "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; - -} - -.hide { - display: none; -} - -img { - border: none; -} - -pre { - width: 98%; - overflow-x: scroll; - text-align: left; - font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; - color: #EE9; -} - -div.logo { - float: left; - padding: 10px; - text-align: center; - font-color: #EE9; - margin: 0 20px 0 20px; - border: 1px solid #494; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - background: #000; /*url("images/camotile2.png");*/ - width: 185px; - -moz-box-shadow: inset 0px 0px 1px 0px #009; - -khtml-box-shadow: inset 0px 0px 1px 0px #009; - box-shadow: inset 0px 0px 1px 0px #009; -} - -div.logo hr { - color: #494; - background: #494; - height: 1px; - border: 0px solid #494; - margin: 10px 0 5px; -} - -div.toolbar { - margin: 0; - padding: 10px; - font-weight: bold; - background: #000; - border: 1px solid #000; - display: none; -} - -div.toolbar a:link { - border: 1px outset #ddddc0; - padding: 0px 5px 1px 5px; - background: #bbf; - text-decoration: none; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - color: #000; -} - -div.toolbar a:visited { - background: #ddf; -} - -div.toolbar a:hover, button:hover{ - border: 1px solid #f60; - background: #030; - color: #f60; -} - -a:active{ - color: #900; -} - -div.routersummaryouter { - float: left; - width: 200px; - margin: 0 0 10px 5px; - padding: 0; - border: 0; - clear: left;/* fixes a bug in Opera */ - text-align: center; - display: block; -} - -div.routersummary { - width: 173px; - padding: 10px; - text-align: center; - border: 1px solid #494; - background: #000 url(images/camotile2.png); - color: #EE9; - font-size: 8pt; - clear: left;/* fixes a bug in Opera */ - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - float: left; - -moz-box-shadow: 0 1px 5px #000; - -khtml-box-shadow: 0 1px 5px #000; - box-shadow: 0 1px 5px #000; -} - -div.routersummary input[type=text] { - text-align: right !important; - -moz-box-shadow: inset 1px 1px 1px 0px #000; - -khtml-box-shadow: inset 1px 1px 1px 0px #000; - box-shadow: inset 1px 1px 1px 0px #000; -} - -div.routersummary hr { - color: #494; - background: #494; - height: 2px; - border-bottom: 1px solid #494; - margin: 8px -10px 7px -10px; - -moz-box-shadow: inset 0px 1px 1px 1px #000; - -khtml-box-shadow: inset 0px 1px 1px 1px #000; - box-shadow: inset 0px 1px 1px 1px #000; -} - -div.routersummary h3 { - border: 0; - font-size: 9.5pt; - letter-spacing: 0.04em; - margin: -7px -10px -8px -10px; - padding: 3px 0 4px 0 !important; - text-transform: uppercase; - -moz-border-radius: 0; - -khtml-border-radius: 0; - border-radius: 0; - background: #000 url('images/header.png') center center ; - background-image: -moz-linear-gradient(top, bottom, from(#050), to(#030), color-stop(7%, #000), color-stop(100%, #050)); -} - -div.routersummary h4 { - border: 0; - border-bottom: 0 !important; - font-size: 8.5pt; - letter-spacing: 0.02em; - margin: -7px -9px -10px -9px !important; - padding: 6px 3px 9px 3px; - background: #000; - text-transform: capitalize; - text-decoration: none !important; - color: #2b2; - background-image: -moz-linear-gradient(top, bottom, from(#000), to(#050), color-stop(10%, #050), color-stop(100%, #004)); - line-height: 100%; -} - -div.routersummary table { - border: 0; - text-align: center !important; - margin: -5px -7px -5px -8px !important; - width: 188px !important; - overflow: hidden; - font-size: 8pt; - padding: 0 -10px; - background-image: none !important; - background-color: transparent !important; -} - -div.routersummary tr { - background-image: none !important; - background-color: transparent !important; - border: 0 !important; -} - -div.routersummary form { - margin: -6px 0 -7px; -} - -div.routersummary form:first-child { - margin: 6px 0 -4px 0 !important; -} - -div.routersummary p { - padding: 0; -} - -div.refresh { - margin-top: -10px !important; - margin-bottom: -4px !important; - padding: 2px 0 0px 0 !important; -} - -div.routersummary a:link, div.routersummary a:visited { - text-shadow: 1px 1px 1px rgba(0, 16, 0, 0.8); - text-shadow: 0px 0px 2px #101 !important; -} - -div.routersummary a:hover { - text-shadow: 0px 0px 1px rgba(255, 96, 0, 0.7); - color: #f60; -} - -div.routersummary td { - padding: 0px 2px 0px 2px; - background-image: none !important; - border: 0 !important; -} - -div routersummary hr:last-child { - margin-top: 5px; - margin-bottom: -5px !important; -} - -div.tunnels { - padding-top: 3px !important; - margin-left: -4px; - text-align: center; -} - -div.tunnels table { - margin: -5px 0 -5px -3px !important; -} - -div.tunnels td { - padding: 1px 0px 1px 0px; -} - -div.tunnels td:first-child { - width: 16px; - text-align: left; - padding-right: 2px; -} - -div.tunnels td:last-child { - text-align: right; - padding-right: 1px; -} - -div.tunnels tr { -/* border: 1px solid #494 !important;*/ -} - -div.warning { - margin: 20px 20px 20px 245px; - padding: 5px 25px 20px 75px; - background: #000; - border: 1px solid #494; - text-align: left; - color: #EE9; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - text-align: justify; - background-image:url("../images/itoopie_sm.png"); - background-position:10px center; - background-repeat:no-repeat; - -moz-box-shadow: inset 0px 0px 0px 1px #f00; - -khtml-box-shadow: inset 0px 0px 0px 1px #f00; - box-shadow: inset 0px 0px 0px 1px #f00; - word-wrap: break-word; -} - -/* console error messages */ - -div.sorry { - margin: -1px 5px 10px 205px; - padding: 20px 20px 20px 75px; - background: #020; - border: 1px solid #494; - -moz-border-radius: 0 0 4px 4px; - -khtml-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; - text-align: justify; - background-image:url("images/errortriangle.png"); - background-position:15px center; - background-repeat:no-repeat; - -moz-box-shadow: inset 0px 0px 0px 1px #d00; - word-wrap: break-word; - font-weight: bold; - color: #EE9; -} - -div.sorry hr { - color: #EE9; - background: #EE9; - height: 1px; - border: 1px solid #EE9; - margin: 10px 0; -} - -div.main { - margin: -1px 5px 5px 205px; - padding: 0 15px 15px 15px; - text-align: left; - color: #EE9; - width: auto; -/* overflow-x: scroll; */ - border: 1px solid #494; - -moz-border-radius: 0 0 4px 4px; - -khtml-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; - background: #000 url(images/scarface.jpg) right bottom no-repeat !important; - min-width: 620px; - -moz-box-shadow: 0 1px 5px #000; -} - -div.main textarea { - background: #000; - color: #EE9; - font: 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; -} - -div.news { - margin: -1px 5px 0px 205px; - padding: 5px 30px 5px 30px; - border: 1px solid #494; - background: #000; - background: #000 url("images/news.png")no-repeat scroll bottom right; - color: #7b7; -/* border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - -khtml-border-radius: 4px 4px 0 0;*/ - font-size: 7.5pt; - text-align: right; - -moz-box-shadow: 0 1px 5px #000; - -khtml-box-shadow: 0 1px 5px #000; - box-shadow: 0 1px 5px #000; - min-width: 580px; -/* height: 164px; - overflow-y: auto;*/ -} - -div.news li { - text-align: justify; - list-style: url('images/info_dark.png'); - list-style: none; - margin: 0; - padding: 5px 5px 5px 0; - vertical-align: middle; - word-wrap: break-word; - color: #494; - font-weight: bold; - font-size: 9.5pt; - border-bottom: 1px dotted #494; - margin-bottom: 5px; - text-transform: capitalize; -} - -div.news h3 { - text-align: left !important; -} - -div.news h4 { - border-bottom: 1px; - border-bottom-style: dotted; - border-bottom-color: #494; - padding: 0 0 0px 0; - margin: 5px 0 10px 0; - font-size: 10pt; - opacity: 1; - text-transform: capitalize; -} - -div.news h4:first-child { - background: url('../images/itoopbullet.png'); - background-repeat: no-repeat; - background-position: right; -} - -div.news p { - margin-top: -5px; - font-size: 8.5pt; - color: #EE9; - margin-bottom: 0; -} - -div.news p:nth-child(n+1) { - margin-top: 5px; -} - -div.news hr { - margin: 8px 0 3px 0; -} - -div.confignav { - padding: 15px 10px !important; - margin: 15px 0; - background: #000 url('images/header.png') center center repeat-x ; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - border: 1px solid #494; - font-size: 9.5pt !important; - font-weight: bold !important; - line-height: 160% !important; - -} - -div.configure { -/* padding: 5px 15px 0 15px; - margin: 10px 0px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - border: 1px solid #494; */ - background: none;/* url(images/camotile2.png);*/ -} - -div.messages { - padding: 10px; - margin: 10px 0 15px 0; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; - border: 1px solid #494; - background: #000 /*url('images/infotile.png') center left no-repeat;*/ - font-weight: bold; - font-size: 9pt; - color: #4f4; -} - -div.messages span.error { - color: #d90; -} - -div.messages span.notice { - font-style: italic; -} - -div.messages li { - text-align: justify !important; - font-weight: bold; - list-style: url(images/warning_dark.png) !important; - margin: 0 5px 0 50px !important; - padding: 0 10px 0 0 !important; - border: 0px !important; -} - -div.graphspanel { - padding: 0; - margin: 15px 0px -15px 0; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border-radius: 4px; -/* border: 1px solid #494;*/ - background: none;/* url(images/camotile.png);*/ - text-align: center; -} - -div.widepanel h3 { - text-align: left !important; -} - -div.graphspanel form { - text-align: left; - padding: 0 15px 0px 15px; -} - -div.graphspanel hr { - margin: 10px -15px 10px -15px; -} - -div.graphspanel img { - border: 1px solid #494; - padding: 3px; - margin: 5px; - text-align: center !important; - background: #000; - - opacity: 0.8; -} - -div.graphspanel img:hover { - border: 1px solid #000; - padding: 3px; - margin: 5px; - text-align: center !important; - background: #000; - -moz-box-shadow: inset 0px 0px 1px 1px #f60; - -khtml-box-shadow: inset 0px 0px 1px 1px #f60; - box-shadow: inset 0px 0px 1px 1px #f60; - opacity: 1; -} - -table { - border-collapse: collapse; - width: 100%; - border: 1px solid #494; - cell-padding: 1px; - font-size: 7pt; - background: #030; - margin: 1px 0; -} - -table hr { - padding: 0px 0; - color: #494; - background: #494; - border: 0px solid #494; - margin: 0px 0px; - height: 1px; - display: none; -} - -th { - padding: 6px 2px; - color: #EE9; - text-align: center; - font-size: 9pt; - background: #000; /*url('images/tabletitledark.png') repeat-x;*/ - background: #000 url('images/header.png') center center repeat-x ; - border-top: 1px solid #494; - border-bottom: 1px solid #494 !important; - line-height: 110%; -} - -tr { - vertical-align: middle; -} - -tr:nth-child(even) { - background: #010;/* url('images/darkerbluetile.png') !important;*/ - vertical-align: middle; -} - -tr:nth-child(odd) { - background: #000800;/* url('images/darkbluetile.png') !important;*/ - vertical-align: middle; -} -/* -tr:last-child { - background: #004 url('images/lightbluetile.png') !important; - font-weight: bold; - border: 1px solid #494 !important; -} -*/ -td { - padding: 4px 6px; - color: #EE9; - vertical-align: middle; - border-top: 1px inset #494; - border-bottom: 1px outset #494; -} - -td img { - padding: 0 1px 0 2px; -} - -tt { - font: bold 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; - color: #FF0; - padding: 0 5px 2px 0; -} - -div.main li { - text-align: left; - list-style: square; - margin: 2px 0px 2px 30px; - padding: 2px 20px 2px 0px; -/* line-height: 150%;*/ - word-wrap: break-word; -} - - -div.main li b { - color: #b70 !important; - letter-spacing: 0.07em; - font-size: 9pt; - text-shadow: 0 1px 1px #700; -} - -.tidylist { - text-align: justify !important; - line-height: 150%; -} - -.tidylist:first-child { -/* padding-top: 5px;*/ -} - -.tidylist:last-child { - padding-bottom: 10px; -} - -.tidylist code { - text-align: left; - font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; - color: #dd0; - padding: 1px 2px; - background: #030; - margin: 0 2px; -} - -ol { - display: inline; - margin: 1px 0 0 0; - padding: 1px 0 0 20px; -} - -ul { -/* display: inline; */ - margin: 0; - padding: 0; -} - -code { - text-align: left; - font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; - color: #dd0; - padding: 1px 2px; -} - -a:link, h2 a:link{ - color: #494; - text-decoration: none; - font-weight: bold; - word-wrap: break-word; -} - -a:visited{ - color: #7b7; - text-decoration: none; - font-weight: bold; - word-wrap: break-word; -} - -a:hover{ - color: #f60; - text-decoration: underline; - font-weight: bold; - word-wrap: break-word; -} - -.links { - padding-bottom: -2px; - text-align: justify; - margin-top: 10px; - margin-bottom: -10px; -} - -.links li { - list-style-image: url("images/link.png") !important; -} - -.links b{ - color: #b70 !important; - letter-spacing: 0.07em; - font-size: 9.5pt; - line-height: 165%; - text-shadow: 0 1px 1px #700; -} - -p { - text-align: justify; - line-height: 160%; -} - -h1 { - text-align: left; - color: #EE9; - padding: 15px 15px 14px; - margin: 0 5px 0px 205px !important; - font-size: 17pt; - font-weight: bold; - font-style: normal; - text-transform: uppercase; - letter-spacing: 0.15em; - text-shadow: 0px 0px 2px #010; - white-space: normal; - background: #000 url("images/scope.png")no-repeat scroll right top; - background: #000 url("images/bg2.png")no-repeat scroll top right; - background: #000 url('images/header.png') center center; - background-image: -moz-linear-gradient(top, bottom, from(#000), to(#030), color-stop(30%, #000), color-stop(100%, #000)); - border: 1px solid #494; - border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - -khtml-border-radius: 4px 4px 0 0; - line-height: 120%; - min-width: 620px; - -moz-box-shadow: 0 1px 5px #000; - -khtml-box-shadow: 0 1px 5px #000; - box-shadow: 0 1px 5px #000; -} - -h2 { - font-size: 12pt; - color: #EE9; - text-shadow: 0px 0px 2px #010; - letter-spacing: 0.05em; - background: #000 url(images/camotile2.png); - background-image: -moz-linear-gradient(top, bottom, from(#000), to(#030), color-stop(30%, #000), color-stop(100%, #000)); - background: #000 url('images/header.png') center center ; - padding: 10px; - wordwrap: none; - border: 1px solid #494; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - vertical-align: middle; - margin: 15px 0 12px 0 !important; - text-transform: uppercase; - word-wrap: break-word; -} - -h2 a:visited { - color: #191; -} - -h2 a:hover { - color: #f60; - text-shadow: 0px 0px 1px rgba(255, 64, 0, 0.7); -} - -h3 { - border: 1px solid #494; - border-left: 5px solid #494; - padding: 5px 6px; - margin: 12px 0 10px 0; - border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - -khtml-border-radius: 0 4px 4px 0; - background: #000 url(images/camotile.png); - background: #000 url('images/header.png') center center ; - text-transform: uppercase; - text-shadow: 0px 0px 2px #010; -} - -h4 { - border-bottom: 1px; - border-bottom-style: solid; - border-bottom-color: #494; - padding: 0 0 10px 0; - margin: 5px 0 10px 0; - font-size: 11pt; -} - -button, button:visited { - font: bold 9pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; - border: 1px outset #191; - padding: 1px 3px; - text-decoration: none; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - font-size: 8pt; - font-weight: bold; - margin: 2px 3px; - text-align: center; - vertical-align: middle; - min-width: 76px; - -moz-box-shadow: inset 0px 1px 1px 0px #494; - -khtml-box-shadow: inset 0px 1px 1px 0px #191; - box-shadow: inset 0px 1px 1px 0px #191; - background: #000; - color: #494; -} - -button:hover { - border: 1px solid #f60; - -moz-box-shadow: inset 0px 1px 1px 0px #EE9; - -khtml-box-shadow: inset 0px 1px 1px 0px #EE9; - box-shadow: inset 0px 1px 1px 0px #EE9; - background: #000; - color: #f60; -} - -button:active { - border: 1px inset #f60; - background: #f60; - color: #EE9; - -moz-box-shadow: inset 0px 0px 0px 0px #f60; - -khtml-box-shadow: inset 0px 0px 0px 0px #f60; - box-shadow: inset 0px 0px 0px 0px #f60; -} - -.underline { - border-bottom: 1px solid #eeeeff; - padding: 5px 0px 5px 0px; - margin: 0px 0px 10px 0px; -} - -.langbox { - margin: 21px 2px 2px 5px; - padding: 7px 10px 5px 10px; - color: #EE9; - font-size: 7pt; - width: 220px; - text-align: right; - float: right; - vertical-align: middle; -} - -.langbox img { - opacity: 0.5; - -moz-box-shadow: 0 0 1px #000; -} - -.langbox img:hover { - opacity: 1; - -moz-box-shadow: 0 0 1px #f60; -} - -hr { - color: #494; - background: #494; - height: 1px; - border: 0px solid #494; - margin: 20px 0 10px; -} - -hr:last-child { - margin-top: 20px; - margin-bottom: 20px; -} - -sidebarlogo { - text-align: center; -} - -input { - border: 1px outset #5f5; - -moz-box-shadow: inset 0px 1px 1px 0px #373; - -khtml-box-shadow: inset 0px 1px 1px 0px #373; - box-shadow: inset 0px 1px 1px 0px #373; - background: #000; - color: #494; - margin: 5px; - font: bold 8pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; - padding: 1px 2px; - text-decoration: none; - min-width: 110px; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; -} - -input:hover { - background: #000; - color: #f60; - border: 1px solid #f60; - -moz-box-shadow: inset 0px 1px 1px 0px #9e9; - -khtml-box-shadow: inset 0px 1px 1px 0px #9e9; - box-shadow: inset 0px 1px 1px 0px #9e9; -} - -input:active { - background: #000; - color: #f30; - border: 1px solid #f30; -} - -input:active { - border: 1px inset #f60; - background: #f60; - color: #EE9; -} - -input[type=text] { - background: #000; - color: #EE9; - margin: 5px 10px; - padding: 4px 2px; - font: bold 8pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; - border: 1px solid #494 !important; - text-decoration: none; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - -moz-box-shadow: inset 1px 1px 1px 0px #000; - -khtml-box-shadow: inset 1px 1px 1px 0px #000; - box-shadow: inset 1px 1px 1px 0px #000; -} - -input[type=text]:active, input[type=text]:hover { - background: #000; -} - -fieldset { -overflow: hidden; -position: relative; -} - -select { - background: #000; - color: #EE9; - margin: 5px 10px; - border: 1px solid #494; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - min-width: 110px; - font: 9pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; - padding: 2px; -} - -textarea { - background: #000; - color: #EE9; - padding: 5px; - margin: 10px; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; - min-height: 100px; - min-width: 97%; - text-align: left; - border: 1px solid #494; -} - -form {} - -.proxyfooter { - margin: 0 20px 10px 240px; - padding: 20px 25px 20px 75px; - font-color: #f00; - font-size: 7pt; - text-align: right !important; - border-radius: 4px; - -moz-border-radius: 4px; - -khtml-border-radius: 4px; - border: 1px solid #000; - display: none; -} - -.statusnotes { - font-style: italic; - font-size: 8pt; - color: #EE9; - text-align: center; - border: 1px solid #494 !important; -/* border-top: 0px !important;*/ - margin: -3px 0 5px 0; - padding: 7px; - background: #010; - -moz-box-shadow: inset 0px 0px 0px 1px #090; - -khtml-box-shadow: inset 0px 0px 0px 1px #090; - box-shadow: inset 0px 0px 0px 1px #090; -/* background: #000 url('images/header.png') repeat-x center center !important;*/ -} - -div.joblog { -/* margin: 15px 0 15px 0; - padding: 5px 20px 10px 20px !important; - border: 1px solid #494; - background-color: #000; - background: #000; url("images/camotile.png");*/ -/* color: #dfd;*/ - border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - -khtml-border-radius: 4px 4px 0 0; - text-align: justify !important; - overflow-x: auto; /* Opera fix */ - } - -div.main li { - text-align: left; - list-style: square; - margin: 2px 0px 2px 30px; - padding: 2px 20px 2px 0px; -/* line-height: 150%;*/ - word-wrap: break-word; -} - -div.joblog li { - word-wrap: break-word !important; - text-align: justify !important; - line-height: 120% !important; - margin: 2px 0px 2px 30px; - padding: 2px 20px 2px 0px; -} - -div.joblog ul { - word-wrap: break-word !important; - text-align: justify; - margin: 0px 0 10px; -} - -div.joblog li:first-child { - margin-top: 0px; -} - -div.joblog li:last-child { -/* margin-bottom: -25px;*/ -} - -div.joblog form:first-child { - margin-top: 10px; -} - -div.joblog table { - margin-top: 15px; -} - -div.joblog p { - line-height: 130%; -} - -.smallhead { - font-size: 7pt -} - -.mediumtags { - font-size: 9pt; -} - -.optbox { - min-width: 16px !important; - max-width: 16px !important; - width: 16px !important; - min-height: 16px; - max-height: 16px; - height: 16px; - opacity: 1.0; - border: 0; - margin: 5px 5px 5px 10px; - padding: 2px; - overflow: hidden; - position: relative; -} - -.optbox:hover { - min-width: 16px !important; - max-width: 16px !important; - width: 16px !important; - min-height: 16px; - max-height: 16px; - height: 16px; - opacity: 1.0; - border: 0; - margin: 5px 5px 5px 10px; - padding: 2px; -} - -.cells { - border: 1px inset #494; - border-left: 1px outset #494; -} - -.tablefooter tr, .tablefooter td { - background: #000 url('images/header.png') repeat-x center center !important; - border-top: 1px solid #494; - border-bottom: 1px solid #494 !important; - font-size: 7pt; - line-height: 110%; -} - -.formaction { - text-align: right; -} - -div.footnote { - text-align: right; - color: #494; - font-size: 7pt; - margin-bottom: -8px !important; -} - -div.footnote hr{ - margin: 10px 0 5px 0 !important; - color: #494; - background: #494; - height: 1px; - border: 0px solid #494; -} - -.topness { - font-size: 7.5pt; - text-align: right; - margin-top: -5px; - margin-bottom: -5px; - margin-right: 5px; -} +/* I2P Theme: Camo aka Dark */ +/* Description: Military Grade. */ +/* Comment: Thanks to Florian Kuhlmann for the hatface images. [ http://www.flickr.com/photos/floriankuhlmann/] +/* Author: dr|z3d */ + +body { + margin: 5px 0px 0 0px; + padding: 0; + text-align: center; + background: #010 url('images/camotile.png') center bottom; + color: #EE9; + font: 9pt/130% "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; + +} + +.hide { + display: none; +} + +img { + border: none; +} + +pre { + width: 98%; + overflow-x: scroll; + text-align: left; + font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; + color: #EE9; +} + +div.logo { + float: left; + padding: 10px; + text-align: center; + font-color: #EE9; + margin: 0 20px 0 20px; + border: 1px solid #494; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: #000; /*url("images/camotile2.png");*/ + width: 185px; + -moz-box-shadow: inset 0px 0px 1px 0px #009; + -khtml-box-shadow: inset 0px 0px 1px 0px #009; + box-shadow: inset 0px 0px 1px 0px #009; +} + +div.logo hr { + color: #494; + background: #494; + height: 1px; + border: 0px solid #494; + margin: 10px 0 5px; +} + +div.toolbar { + margin: 0; + padding: 10px; + font-weight: bold; + background: #000; + border: 1px solid #000; + display: none; +} + +div.toolbar a:link { + border: 1px outset #ddddc0; + padding: 0px 5px 1px 5px; + background: #bbf; + text-decoration: none; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + color: #000; +} + +div.toolbar a:visited { + background: #ddf; +} + +div.toolbar a:hover, button:hover{ + border: 1px solid #f60; + background: #030; + color: #f60; +} + +a:active{ + color: #900; +} + +div.routersummaryouter { + float: left; + width: 200px; + margin: 0 0 10px 5px; + padding: 0; + border: 0; + clear: left;/* fixes a bug in Opera */ + text-align: center; + display: block; +} + +div.routersummary { + width: 173px; + padding: 10px; + text-align: center; + border: 1px solid #494; + background: #000 url(images/camotile2.png); + color: #EE9; + font-size: 8pt; + clear: left;/* fixes a bug in Opera */ + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + float: left; + -moz-box-shadow: 0 1px 5px #000; + -khtml-box-shadow: 0 1px 5px #000; + box-shadow: 0 1px 5px #000; +} + +div.routersummary input[type=text] { + text-align: right !important; + -moz-box-shadow: inset 1px 1px 1px 0px #000; + -khtml-box-shadow: inset 1px 1px 1px 0px #000; + box-shadow: inset 1px 1px 1px 0px #000; +} + +div.routersummary hr { + color: #494; + background: #494; + height: 2px; + border-bottom: 1px solid #494; + margin: 8px -10px 7px -10px; + -moz-box-shadow: inset 0px 1px 1px 1px #000; + -khtml-box-shadow: inset 0px 1px 1px 1px #000; + box-shadow: inset 0px 1px 1px 1px #000; +} + +div.routersummary h3 { + border: 0; + font-size: 9.5pt; + letter-spacing: 0.04em; + margin: -7px -10px -8px -10px; + padding: 3px 0 4px 0 !important; + text-transform: uppercase; + -moz-border-radius: 0; + -khtml-border-radius: 0; + border-radius: 0; + background: #000 url('images/header.png') center center ; + background-image: -moz-linear-gradient(top, bottom, from(#050), to(#030), color-stop(7%, #000), color-stop(100%, #050)); +} + +div.routersummary h4 { + border: 0; + border-bottom: 0 !important; + font-size: 8.5pt; + letter-spacing: 0.02em; + margin: -7px -9px -10px -9px !important; + padding: 6px 3px 9px 3px; + background: #000; + text-transform: capitalize; + text-decoration: none !important; + color: #2b2; + background-image: -moz-linear-gradient(top, bottom, from(#000), to(#050), color-stop(10%, #050), color-stop(100%, #004)); + line-height: 100%; +} + +div.routersummary table { + border: 0; + text-align: center !important; + margin: -5px -7px -5px -8px !important; + width: 188px !important; + overflow: hidden; + font-size: 8pt; + padding: 0 -10px; + background-image: none !important; + background-color: transparent !important; +} + +div.routersummary tr { + background-image: none !important; + background-color: transparent !important; + border: 0 !important; +} + +div.routersummary form { + margin: -6px 0 -7px; +} + +div.routersummary form:first-child { + margin: 6px 0 -4px 0 !important; +} + +div.routersummary p { + padding: 0; +} + +div.refresh { + margin-top: -10px !important; + margin-bottom: -4px !important; + padding: 2px 0 0px 0 !important; +} + +div.routersummary a:link, div.routersummary a:visited { + text-shadow: 1px 1px 1px rgba(0, 16, 0, 0.8); + text-shadow: 0px 0px 2px #101 !important; +} + +div.routersummary a:hover { + text-shadow: 0px 0px 1px rgba(255, 96, 0, 0.7); + color: #f60; +} + +div.routersummary td { + padding: 0px 2px 0px 2px; + background-image: none !important; + border: 0 !important; +} + +div routersummary hr:last-child { + margin-top: 5px; + margin-bottom: -5px !important; +} + +div.tunnels { + padding-top: 3px !important; + margin-left: -4px; + text-align: center; +} + +div.tunnels table { + margin: -5px 0 -5px -3px !important; +} + +div.tunnels td { + padding: 1px 0px 1px 0px; +} + +div.tunnels td:first-child { + width: 16px; + text-align: left; + padding-right: 2px; +} + +div.tunnels td:last-child { + text-align: right; + padding-right: 1px; +} + +div.tunnels tr { +/* border: 1px solid #494 !important;*/ +} + +div.warning { + margin: 20px 20px 20px 245px; + padding: 5px 25px 20px 75px; + background: #000; + border: 1px solid #494; + text-align: left; + color: #EE9; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + text-align: justify; + background-image:url("../images/itoopie_sm.png"); + background-position:10px center; + background-repeat:no-repeat; + -moz-box-shadow: inset 0px 0px 0px 1px #f00; + -khtml-box-shadow: inset 0px 0px 0px 1px #f00; + box-shadow: inset 0px 0px 0px 1px #f00; + word-wrap: break-word; +} + +/* console error messages */ + +div.sorry { + margin: -1px 5px 10px 205px; + padding: 20px 20px 20px 75px; + background: #020; + border: 1px solid #494; + -moz-border-radius: 0 0 4px 4px; + -khtml-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; + text-align: justify; + background-image:url("images/errortriangle.png"); + background-position:15px center; + background-repeat:no-repeat; + -moz-box-shadow: inset 0px 0px 0px 1px #d00; + word-wrap: break-word; + font-weight: bold; + color: #EE9; +} + +div.sorry hr { + color: #EE9; + background: #EE9; + height: 1px; + border: 1px solid #EE9; + margin: 10px 0; +} + +div.main { + margin: -1px 5px 5px 205px; + padding: 0 15px 15px 15px; + text-align: left; + color: #EE9; + width: auto; +/* overflow-x: scroll; */ + border: 1px solid #494; + -moz-border-radius: 0 0 4px 4px; + -khtml-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; + background: #000 url(images/scarface.jpg) right bottom no-repeat !important; + min-width: 620px; + -moz-box-shadow: 0 1px 5px #000; +} + +div.main textarea { + background: #000; + color: #EE9; + font: 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; +} + +div.news { + margin: -1px 5px 0px 205px; + padding: 5px 30px 5px 30px; + border: 1px solid #494; + background: #000; + background: #000 url("images/news.png")no-repeat scroll bottom right; + color: #7b7; +/* border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0;*/ + font-size: 7.5pt; + text-align: right; + -moz-box-shadow: 0 1px 5px #000; + -khtml-box-shadow: 0 1px 5px #000; + box-shadow: 0 1px 5px #000; + min-width: 580px; +/* height: 164px; + overflow-y: auto;*/ +} + +div.news li { + text-align: justify; + list-style: url('images/info_dark.png'); + list-style: none; + margin: 0; + padding: 5px 5px 5px 0; + vertical-align: middle; + word-wrap: break-word; + color: #494; + font-weight: bold; + font-size: 9.5pt; + border-bottom: 1px dotted #494; + margin-bottom: 5px; + text-transform: capitalize; +} + +div.news h3 { + text-align: left !important; +} + +div.news h4 { + border-bottom: 1px; + border-bottom-style: dotted; + border-bottom-color: #494; + padding: 0 0 0px 0; + margin: 5px 0 10px 0; + font-size: 10pt; + opacity: 1; + text-transform: capitalize; +} + +div.news h4:first-child { + background: url('../images/itoopbullet.png'); + background-repeat: no-repeat; + background-position: right; +} + +div.news p { + margin-top: -5px; + font-size: 8.5pt; + color: #EE9; + margin-bottom: 0; +} + +div.news p:nth-child(n+1) { + margin-top: 5px; +} + +div.news hr { + margin: 8px 0 3px 0; +} + +div.confignav { + padding: 15px 10px !important; + margin: 15px 0; + background: #000 url('images/header.png') center center repeat-x ; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #494; + font-size: 9.5pt !important; + font-weight: bold !important; + line-height: 160% !important; + +} + +div.configure { +/* padding: 5px 15px 0 15px; + margin: 10px 0px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #494; */ + background: none;/* url(images/camotile2.png);*/ +} + +div.messages { + padding: 10px; + margin: 10px 0 15px 0; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #494; + background: #000 /*url('images/infotile.png') center left no-repeat;*/ + font-weight: bold; + font-size: 9pt; + color: #4f4; +} + +div.messages span.error { + color: #d90; +} + +div.messages span.notice { + font-style: italic; +} + +div.messages li { + text-align: justify !important; + font-weight: bold; + list-style: url(images/warning_dark.png) !important; + margin: 0 5px 0 50px !important; + padding: 0 10px 0 0 !important; + border: 0px !important; +} + +div.graphspanel { + padding: 0; + margin: 15px 0px -15px 0; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +/* border: 1px solid #494;*/ + background: none;/* url(images/camotile.png);*/ + text-align: center; +} + +div.widepanel h3 { + text-align: left !important; +} + +div.graphspanel form { + text-align: left; + padding: 0 15px 0px 15px; +} + +div.graphspanel hr { + margin: 10px -15px 10px -15px; +} + +div.graphspanel img { + border: 1px solid #494; + padding: 3px; + margin: 5px; + text-align: center !important; + background: #000; + + opacity: 0.8; +} + +div.graphspanel img:hover { + border: 1px solid #000; + padding: 3px; + margin: 5px; + text-align: center !important; + background: #000; + -moz-box-shadow: inset 0px 0px 1px 1px #f60; + -khtml-box-shadow: inset 0px 0px 1px 1px #f60; + box-shadow: inset 0px 0px 1px 1px #f60; + opacity: 1; +} + +table { + border-collapse: collapse; + width: 100%; + border: 1px solid #494; + cell-padding: 1px; + font-size: 7pt; + background: #030; + margin: 1px 0; +} + +table hr { + padding: 0px 0; + color: #494; + background: #494; + border: 0px solid #494; + margin: 0px 0px; + height: 1px; + display: none; +} + +th { + padding: 6px 2px; + color: #EE9; + text-align: center; + font-size: 9pt; + background: #000; /*url('images/tabletitledark.png') repeat-x;*/ + background: #000 url('images/header.png') center center repeat-x ; + border-top: 1px solid #494; + border-bottom: 1px solid #494 !important; + line-height: 110%; +} + +tr { + vertical-align: middle; +} + +tr:nth-child(even) { + background: #010;/* url('images/darkerbluetile.png') !important;*/ + vertical-align: middle; +} + +tr:nth-child(odd) { + background: #000800;/* url('images/darkbluetile.png') !important;*/ + vertical-align: middle; +} +/* +tr:last-child { + background: #004 url('images/lightbluetile.png') !important; + font-weight: bold; + border: 1px solid #494 !important; +} +*/ +td { + padding: 4px 6px; + color: #EE9; + vertical-align: middle; + border-top: 1px inset #494; + border-bottom: 1px outset #494; +} + +td img { + padding: 0 1px 0 2px; +} + +tt { + font: bold 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; + color: #FF0; + padding: 0 5px 2px 0; +} + +div.main li { + text-align: left; + list-style: square; + margin: 2px 0px 2px 30px; + padding: 2px 20px 2px 0px; +/* line-height: 150%;*/ + word-wrap: break-word; +} + + +div.main li b { + color: #b70 !important; + letter-spacing: 0.07em; + font-size: 9pt; + text-shadow: 0 1px 1px #700; +} + +.tidylist { + text-align: justify !important; + line-height: 150%; +} + +.tidylist:first-child { +/* padding-top: 5px;*/ +} + +.tidylist:last-child { + padding-bottom: 10px; +} + +.tidylist code { + text-align: left; + font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; + color: #dd0; + padding: 1px 2px; + background: #030; + margin: 0 2px; +} + +ol { + display: inline; + margin: 1px 0 0 0; + padding: 1px 0 0 20px; +} + +ul { +/* display: inline; */ + margin: 0; + padding: 0; +} + +code { + text-align: left; + font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; + color: #dd0; + padding: 1px 2px; +} + +a:link, h2 a:link{ + color: #494; + text-decoration: none; + font-weight: bold; + word-wrap: break-word; +} + +a:visited{ + color: #7b7; + text-decoration: none; + font-weight: bold; + word-wrap: break-word; +} + +a:hover{ + color: #f60; + text-decoration: underline; + font-weight: bold; + word-wrap: break-word; +} + +.links { + padding-bottom: -2px; + text-align: justify; + margin-top: 10px; + margin-bottom: -10px; +} + +.links li { + list-style-image: url("images/link.png") !important; +} + +.links b{ + color: #b70 !important; + letter-spacing: 0.07em; + font-size: 9.5pt; + line-height: 165%; + text-shadow: 0 1px 1px #700; +} + +p { + text-align: justify; + line-height: 160%; +} + +h1 { + text-align: left; + color: #EE9; + padding: 15px 15px 14px; + margin: 0 5px 0px 205px !important; + font-size: 17pt; + font-weight: bold; + font-style: normal; + text-transform: uppercase; + letter-spacing: 0.15em; + text-shadow: 0px 0px 2px #010; + white-space: normal; + background: #000 url("images/scope.png")no-repeat scroll right top; + background: #000 url("images/bg2.png")no-repeat scroll top right; + background: #000 url('images/header.png') center center; + background-image: -moz-linear-gradient(top, bottom, from(#000), to(#030), color-stop(30%, #000), color-stop(100%, #000)); + border: 1px solid #494; + border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + line-height: 120%; + min-width: 620px; + -moz-box-shadow: 0 1px 5px #000; + -khtml-box-shadow: 0 1px 5px #000; + box-shadow: 0 1px 5px #000; +} + +h2 { + font-size: 12pt; + color: #EE9; + text-shadow: 0px 0px 2px #010; + letter-spacing: 0.05em; + background: #000 url(images/camotile2.png); + background-image: -moz-linear-gradient(top, bottom, from(#000), to(#030), color-stop(30%, #000), color-stop(100%, #000)); + background: #000 url('images/header.png') center center ; + padding: 10px; + wordwrap: none; + border: 1px solid #494; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + vertical-align: middle; + margin: 15px 0 12px 0 !important; + text-transform: uppercase; + word-wrap: break-word; +} + +h2 a:visited { + color: #191; +} + +h2 a:hover { + color: #f60; + text-shadow: 0px 0px 1px rgba(255, 64, 0, 0.7); +} + +h3 { + border: 1px solid #494; + border-left: 5px solid #494; + padding: 5px 6px; + margin: 12px 0 10px 0; + border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + -khtml-border-radius: 0 4px 4px 0; + background: #000 url(images/camotile.png); + background: #000 url('images/header.png') center center ; + text-transform: uppercase; + text-shadow: 0px 0px 2px #010; +} + +h4 { + border-bottom: 1px; + border-bottom-style: solid; + border-bottom-color: #494; + padding: 0 0 10px 0; + margin: 5px 0 10px 0; + font-size: 11pt; +} + +button, button:visited { + font: bold 9pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; + border: 1px outset #191; + padding: 1px 3px; + text-decoration: none; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + font-size: 8pt; + font-weight: bold; + margin: 2px 3px; + text-align: center; + vertical-align: middle; + min-width: 76px; + -moz-box-shadow: inset 0px 1px 1px 0px #494; + -khtml-box-shadow: inset 0px 1px 1px 0px #191; + box-shadow: inset 0px 1px 1px 0px #191; + background: #000; + color: #494; +} + +button:hover { + border: 1px solid #f60; + -moz-box-shadow: inset 0px 1px 1px 0px #EE9; + -khtml-box-shadow: inset 0px 1px 1px 0px #EE9; + box-shadow: inset 0px 1px 1px 0px #EE9; + background: #000; + color: #f60; +} + +button:active { + border: 1px inset #f60; + background: #f60; + color: #EE9; + -moz-box-shadow: inset 0px 0px 0px 0px #f60; + -khtml-box-shadow: inset 0px 0px 0px 0px #f60; + box-shadow: inset 0px 0px 0px 0px #f60; +} + +.underline { + border-bottom: 1px solid #eeeeff; + padding: 5px 0px 5px 0px; + margin: 0px 0px 10px 0px; +} + +.langbox { + margin: 21px 2px 2px 5px; + padding: 7px 10px 5px 10px; + color: #EE9; + font-size: 7pt; + width: 220px; + text-align: right; + float: right; + vertical-align: middle; +} + +.langbox img { + opacity: 0.5; + -moz-box-shadow: 0 0 1px #000; +} + +.langbox img:hover { + opacity: 1; + -moz-box-shadow: 0 0 1px #f60; +} + +hr { + color: #494; + background: #494; + height: 1px; + border: 0px solid #494; + margin: 20px 0 10px; +} + +hr:last-child { + margin-top: 20px; + margin-bottom: 20px; +} + +sidebarlogo { + text-align: center; +} + +input { + border: 1px outset #5f5; + -moz-box-shadow: inset 0px 1px 1px 0px #373; + -khtml-box-shadow: inset 0px 1px 1px 0px #373; + box-shadow: inset 0px 1px 1px 0px #373; + background: #000; + color: #494; + margin: 5px; + font: bold 8pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; + padding: 1px 2px; + text-decoration: none; + min-width: 110px; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; +} + +input:hover { + background: #000; + color: #f60; + border: 1px solid #f60; + -moz-box-shadow: inset 0px 1px 1px 0px #9e9; + -khtml-box-shadow: inset 0px 1px 1px 0px #9e9; + box-shadow: inset 0px 1px 1px 0px #9e9; +} + +input:active { + background: #000; + color: #f30; + border: 1px solid #f30; +} + +input:active { + border: 1px inset #f60; + background: #f60; + color: #EE9; +} + +input[type=text], input[type=password] { + background: #000; + color: #EE9; + margin: 5px 10px; + padding: 4px 2px; + font: bold 8pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; + border: 1px solid #494 !important; + text-decoration: none; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + -moz-box-shadow: inset 1px 1px 1px 0px #000; + -khtml-box-shadow: inset 1px 1px 1px 0px #000; + box-shadow: inset 1px 1px 1px 0px #000; +} + +input[type=text]:active, input[type=text]:hover, input[type=password]:active, input[type=password]:hover { + background: #000; +} + +fieldset { +overflow: hidden; +position: relative; +} + +select { + background: #000; + color: #EE9; + margin: 5px 10px; + border: 1px solid #494; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + min-width: 110px; + font: 9pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; + padding: 2px; +} + +textarea { + background: #000; + color: #EE9; + padding: 5px; + margin: 10px; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + font: 9pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; + min-height: 100px; + min-width: 97%; + text-align: left; + border: 1px solid #494; +} + +form {} + +.proxyfooter { + margin: 0 20px 10px 240px; + padding: 20px 25px 20px 75px; + font-color: #f00; + font-size: 7pt; + text-align: right !important; + border-radius: 4px; + -moz-border-radius: 4px; + -khtml-border-radius: 4px; + border: 1px solid #000; + display: none; +} + +.statusnotes { + font-style: italic; + font-size: 8pt; + color: #EE9; + text-align: center; + border: 1px solid #494 !important; +/* border-top: 0px !important;*/ + margin: -3px 0 5px 0; + padding: 7px; + background: #010; + -moz-box-shadow: inset 0px 0px 0px 1px #090; + -khtml-box-shadow: inset 0px 0px 0px 1px #090; + box-shadow: inset 0px 0px 0px 1px #090; +/* background: #000 url('images/header.png') repeat-x center center !important;*/ +} + +div.joblog { +/* margin: 15px 0 15px 0; + padding: 5px 20px 10px 20px !important; + border: 1px solid #494; + background-color: #000; + background: #000; url("images/camotile.png");*/ +/* color: #dfd;*/ + border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + text-align: justify !important; + overflow-x: auto; /* Opera fix */ + } + +div.main li { + text-align: left; + list-style: square; + margin: 2px 0px 2px 30px; + padding: 2px 20px 2px 0px; +/* line-height: 150%;*/ + word-wrap: break-word; +} + +div.joblog li { + word-wrap: break-word !important; + text-align: justify !important; + line-height: 120% !important; + margin: 2px 0px 2px 30px; + padding: 2px 20px 2px 0px; +} + +div.joblog ul { + word-wrap: break-word !important; + text-align: justify; + margin: 0px 0 10px; +} + +div.joblog li:first-child { + margin-top: 0px; +} + +div.joblog li:last-child { +/* margin-bottom: -25px;*/ +} + +div.joblog form:first-child { + margin-top: 10px; +} + +div.joblog table { + margin-top: 15px; +} + +div.joblog p { + line-height: 130%; +} + +.smallhead { + font-size: 7pt +} + +.mediumtags { + font-size: 9pt; +} + +.optbox { + min-width: 16px !important; + max-width: 16px !important; + width: 16px !important; + min-height: 16px; + max-height: 16px; + height: 16px; + opacity: 1.0; + border: 0; + margin: 5px 5px 5px 10px; + padding: 2px; + overflow: hidden; + position: relative; +} + +.optbox:hover { + min-width: 16px !important; + max-width: 16px !important; + width: 16px !important; + min-height: 16px; + max-height: 16px; + height: 16px; + opacity: 1.0; + border: 0; + margin: 5px 5px 5px 10px; + padding: 2px; +} + +.cells { + border: 1px inset #494; + border-left: 1px outset #494; +} + +.tablefooter tr, .tablefooter td { + background: #000 url('images/header.png') repeat-x center center !important; + border-top: 1px solid #494; + border-bottom: 1px solid #494 !important; + font-size: 7pt; + line-height: 110%; +} + +.formaction { + text-align: right; +} + +div.footnote { + text-align: right; + color: #494; + font-size: 7pt; + margin-bottom: -8px !important; +} + +div.footnote hr{ + margin: 10px 0 5px 0 !important; + color: #494; + background: #494; + height: 1px; + border: 0px solid #494; +} + +.topness { + font-size: 7.5pt; + text-align: right; + margin-top: -5px; + margin-bottom: -5px; + margin-right: 5px; +} diff --git a/installer/resources/themes/console/light/console.css b/installer/resources/themes/console/light/console.css index 6e6a2df84..fe8b61cb2 100644 --- a/installer/resources/themes/console/light/console.css +++ b/installer/resources/themes/console/light/console.css @@ -921,7 +921,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; diff --git a/installer/resources/themes/console/midnight/console.css b/installer/resources/themes/console/midnight/console.css index 4dc8038f7..fbb7006f1 100644 --- a/installer/resources/themes/console/midnight/console.css +++ b/installer/resources/themes/console/midnight/console.css @@ -755,7 +755,7 @@ input { vertical-align: middle; } -input[type=text] { +input[type=text], input[type=password] { margin: 3px 5px 3px 5px; vertical-align: middle; } @@ -765,7 +765,7 @@ select { vertical-align: middle; } -input[type=text], select { +input[type=text], input[type=password] select { background: #001; color: #eef; border: 1px solid #99f; 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/data/i2np/I2NPMessageReader.java b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java index fd3e7063b..74b93b34f 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java @@ -161,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) {} } diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index a8b5395b0..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; @@ -395,10 +404,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/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() { 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..8bef2776d 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -50,9 +50,9 @@ import net.i2p.util.SimpleTimer; * * @author jrandom */ -public class ClientConnectionRunner { +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<MessageId> _acceptedPending; /** thingy that does stuff */ - private I2CPMessageReader _reader; + protected I2CPMessageReader _reader; /** just for this destination */ private SessionKeyManager _sessionKeyManager; /** @@ -109,7 +109,7 @@ public 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); @@ -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/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java index 7a7d448ea..5dc5c6506 100644 --- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java @@ -24,13 +24,13 @@ import net.i2p.util.Log; * * @author jrandom */ -public class ClientListenerRunner implements Runnable { - protected Log _log; - protected RouterContext _context; - protected ClientManager _manager; +class ClientListenerRunner implements Runnable { + 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 @@ public 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 @@ public 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 @@ public 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 @@ public 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 7d866ab0b..a534bdfb1 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; @@ -39,13 +43,18 @@ import net.i2p.util.Log; * * @author jrandom */ -public class ClientManager { - private Log _log; +class ClientManager { + private final Log _log; private ClientListenerRunner _listener; - private ClientListenerRunner _internalListener; 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"; + /** 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; @@ -53,10 +62,10 @@ public 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); @@ -64,16 +73,16 @@ 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(); - _internalListener = new InternalClientListenerRunner(_ctx, this, port); - t = new I2PThread(_internalListener); - t.setName("ClientListener:" + port + "-i"); - t.setDaemon(true); - t.start(); + if (!_ctx.getBooleanProperty(PROP_DISABLE_EXTERNAL)) { + // 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(); + } + _isStarted = true; } public void restart() { @@ -95,9 +104,10 @@ public class ClientManager { } public void shutdown() { + _isStarted = false; _log.info("Shutting down the ClientManager"); - _listener.stopListening(); - _internalListener.stopListening(); + if (_listener != null) + _listener.stopListening(); Set<ClientConnectionRunner> runners = new HashSet(); synchronized (_runners) { for (Iterator<ClientConnectionRunner> iter = _runners.values().iterator(); iter.hasNext();) { @@ -117,7 +127,28 @@ public class ClientManager { } } - public boolean isAlive() { return _listener.isListening(); } + /** + * The InternalClientManager interface. + * Connects 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 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(); + 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 _isStarted && (_listener == null || _listener.isListening()); + } public void registerConnection(ClientConnectionRunner runner) { synchronized (_pendingRunners) { @@ -469,8 +500,8 @@ public 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/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/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/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<I2CPMessage> _in; + private final BlockingQueue<I2CPMessage> _out; + + public I2CPMessageQueueImpl(BlockingQueue<I2CPMessage> in, BlockingQueue<I2CPMessage> 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..758e8221e --- /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 + */ +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, false)); + _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"); + } + +} 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; + } +} 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); diff --git a/router/java/src/net/i2p/router/transport/Addresses.java b/router/java/src/net/i2p/router/transport/Addresses.java deleted file mode 100644 index dab8cfc6d..000000000 --- a/router/java/src/net/i2p/router/transport/Addresses.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.i2p.router.transport; - -/* - * public domain - */ - -import java.net.InetAddress; -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; - - -/** - * Get the local addresses - * - * @author zzz - */ -public 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]; - return null; - } - - /** - * @return an array of all addresses, excluding - * IPv6, local, broadcast, multicast, etc. - */ - public static String[] getAddresses() { - 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]); - } - } catch (UnknownHostException e) {} - - try { - for(Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces(); ifcs.hasMoreElements();) { - NetworkInterface ifc = ifcs.nextElement(); - for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { - InetAddress addr = addrs.nextElement(); - add(rv, addr); - } - } - } catch (SocketException e) {} - - String[] rva = rv.toArray(new String[rv.size()]); - Arrays.sort(rva); - return rva; - } - - private static void add(Set<String> set, InetAddress ia) { - if (ia.isAnyLocalAddress() || - ia.isLinkLocalAddress() || - ia.isLoopbackAddress() || - ia.isMulticastAddress() || - 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); - } - - public static void main(String[] args) { - String[] a = getAddresses(); - for (String s : a) - System.err.println("Address: " + s); - } -} 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 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; 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 bda286d95..62d57810e 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -437,8 +437,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. @@ -453,12 +455,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(); } @@ -475,12 +473,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 @@ -488,6 +482,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 {