propagate from branch 'i2p.i2p' (head 3d405c867f6903bf1d69b04c1daebf3146882525)

to branch 'i2p.i2p.zzz.test4' (head bfd85b10fdd1542526a4b9c53e5d4a733087f317)
This commit is contained in:
zzz
2010-12-15 15:09:48 +00:00
72 changed files with 2147 additions and 736 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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<Destination> dests;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1

View File

@ -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;
}
if (_maxWaitTime > 0)
SimpleScheduler.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
synchronized (_waitingSockets) {
_waitingSockets.add(s);
_waitingSockets.notifyAll();
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) {}
}
}
/**
* Blocking runner, used during the connection establishment whenever we
* are not using the queued builders.
*
* 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
*/
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,37 +643,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
try {
s.close();
} catch (IOException 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;
}
//_log.error("Could not close socket", ex);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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('/'));
if (!jumphost.endsWith(".i2p"))
continue;
if (!jumphost.endsWith(".b32.i2p")) {
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost);
if (dest == null) continue;
}
out.write("<br><a href=\"".getBytes());
out.write(jurl.getBytes());

View File

@ -25,7 +25,7 @@ import net.i2p.util.Log;
* @since 0.8.2
*/
public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelHTTPClientBase.class);
protected final List<String> _proxyList;
protected final static byte[] ERR_NO_OUTPROXY =

View File

@ -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"+
"<html><head><title>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();
@ -130,13 +145,24 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
}
long afterHandle = getTunnel().getContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort +
" [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
} catch (SocketException ex) {
try {
// Send a 503, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network.
socket.getOutputStream().write(ERR_UNAVAILABLE);
} catch (IOException ioe) {}
try {
socket.close();
} catch (IOException ioe) {
} catch (IOException ioe) {}
if (_log.shouldLog(Log.ERROR))
_log.error("Error while closing the received i2p con", ex);
}
_log.error("Error connecting to HTTP server " + remoteHost + ':' + remotePort, ex);
} catch (IOException ex) {
try {
socket.close();
@ -150,12 +176,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if (_log.shouldLog(Log.ERROR))
_log.error("OOM in HTTP server", oom);
}
long afterHandle = getTunnel().getContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
private static class CompressedRequestor implements Runnable {
@ -169,6 +189,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
_headers = headers;
_ctx = ctx;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Compressed requestor running");
@ -183,7 +204,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();
@ -233,14 +254,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 +303,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;

View File

@ -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;
}

View File

@ -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) {
} catch (IOException ioe) {}
if (_log.shouldLog(Log.ERROR))
_log.error("Error while closing the received i2p con", ex);
}
_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();
}

View File

@ -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,28 +269,47 @@ 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();
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");
}
} else {
I2PServerSocket i2pS_S = sockMgr.getServerSocket();
while (true) {
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");
new I2PAppThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
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);
@ -289,37 +318,57 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
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);
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort +
" [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
} catch (SocketException ex) {
try {
socket.close();
} 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);
}
}
}

View File

@ -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 */

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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,6 +993,7 @@ public class IndexBean {
config.setProperty("name", _name);
if (_description != null)
config.setProperty("description", _description);
if (!_context.isRouterContext()) {
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) {
@ -936,6 +1001,7 @@ public class IndexBean {
} 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);
}
}

View File

@ -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>):

View File

@ -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,17 +359,62 @@
</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">

View File

@ -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)

View File

@ -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";

View File

@ -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 {
+ "&amp;periodCount=" + (3 * _periodCount )
+ "&amp;width=" + (3 * _width)
+ "&amp;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();
}

View File

@ -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";
}

View File

@ -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,8 +163,11 @@ public class RouterConsoleRunner {
List<String> notStarted = new ArrayList();
WebApplicationHandler baseHandler = null;
try {
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
int boundAddresses = 0;
// add standard listeners
if (_listenPort != null) {
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
while (tok.hasMoreTokens()) {
String host = tok.nextToken().trim();
try {
@ -110,8 +180,46 @@ public class RouterConsoleRunner {
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
}
}
}
// add SSL listeners
int sslPort = 0;
if (_sslListenPort != null) {
try {
sslPort = Integer.parseInt(_sslListenPort);
} catch (NumberFormatException nfe) {}
if (sslPort <= 0)
System.err.println("Bad routerconsole SSL port " + _sslListenPort);
}
if (sslPort > 0) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
File keyStore = new File(ctx.getConfigDir(), "keystore/console.ks");
if (verifyKeyStore(keyStore)) {
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
while (tok.hasMoreTokens()) {
String host = tok.nextToken().trim();
// doing it this way means we don't have to escape an IPv6 host with []
InetAddrPort iap = new InetAddrPort(host, sslPort);
try {
SslListener ssll = new SslListener(iap);
// the keystore path and password
ssll.setKeystore(keyStore.getAbsolutePath());
ssll.setPassword(ctx.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD));
// the X.509 cert password (if not present, verifyKeyStore() returned false)
ssll.setKeyPassword(ctx.getProperty(PROP_KEY_PASSWORD, "thisWontWork"));
_server.addListener(ssll);
boundAddresses++;
} catch (Exception e) { // probably no exceptions at this point
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + " for SSL: " + e);
}
}
} else {
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
}
}
if (boundAddresses <= 0) {
System.err.println("Unable to bind routerconsole to any address on port " + _listenPort);
System.err.println("Unable to bind routerconsole to any address on port " + _listenPort + (sslPort > 0 ? (" or SSL port " + sslPort) : ""));
return;
}
_server.setRootWebApp(ROUTERCONSOLE);
@ -201,6 +309,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) {

View File

@ -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());
%>

View File

@ -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

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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" />

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}

View File

@ -39,8 +39,10 @@ import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.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 +68,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** currently granted lease set, or null */
private LeaseSet _leaseSet;
/** hostname of router */
/** hostname of router - will be null if in RouterContext */
protected String _hostname;
/** port num to router */
/** port num to router - will be 0 if in RouterContext */
protected int _portNum;
/** socket for comm */
protected Socket _socket;
@ -79,6 +81,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** where we pipe our messages */
protected /* FIXME final FIXME */OutputStream _out;
/**
* Used for internal connections to the router.
* If this is set, _socket, _writer, and _out will be null.
* @since 0.8.3
*/
protected I2CPMessageQueue _queue;
/** who we send events to */
protected I2PSessionListener _sessionListener;
@ -122,6 +131,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private long _lastActivity;
private boolean _isReduced;
/** SSL interface (only) @since 0.8.3 */
protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
void dateUpdated() {
_dateReceived = true;
synchronized (_dateReceivedLock) {
@ -172,6 +184,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected void loadConfig(Properties options) {
_options = new Properties();
_options.putAll(filter(options));
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 {
@ -182,9 +198,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
+ 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 +289,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,8 +298,20 @@ 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);
// 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) {
@ -296,6 +321,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_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 +463,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 +529,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 +548,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,8 +602,13 @@ 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");
else if (_queue != null)
_queue.offer(message); // internal
else if (_writer == null)
throw new I2PSessionException("Already closed");
else
_writer.addMessage(message);
}
@ -581,8 +621,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,6 +670,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_log.warn("Error destroying the session", ipe);
}
}
// SimpleSession does not initialize
if (_availabilityNotifier != null)
_availabilityNotifier.stopNotifying();
_closed = true;
_closing = false;
@ -649,6 +690,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 +711,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,11 +780,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
buf.append(s);
else
buf.append(getClass().getSimpleName());
buf.append(" #");
if (_sessionId != null)
buf.append(_sessionId.getSessionId());
else
buf.append("n/a");
buf.append(" #").append(_sessionId.getSessionId());
buf.append("]: ");
return buf.toString();
}

View File

@ -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.
@ -44,12 +46,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,15 +67,22 @@ 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);
// 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);
@ -82,6 +91,8 @@ class I2PSimpleSession extends I2PSessionImpl2 {
_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) {

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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]";
}
}

View File

@ -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();
}
}
}
}
}
}

View File

@ -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>

View File

@ -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");
}
*****/
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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")) {

View File

@ -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();
}

View File

@ -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")) {

View File

@ -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

View File

@ -72,16 +72,28 @@
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- 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>

View File

@ -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) {}
}

View File

@ -395,10 +395,8 @@ public class JobQueue {
for (int i = _queueRunners.size(); i < numThreads; i++) {
JobQueueRunner runner = new JobQueueRunner(_context, i);
_queueRunners.put(Integer.valueOf(i), runner);
Thread t = new I2PThread(runner);
t.setName("JobQueue"+(_runnerId++));
Thread t = new I2PThread(runner, "JobQueue " + (++_runnerId) + '/' + numThreads, false);
//t.setPriority(I2PThread.MAX_PRIORITY-1);
t.setDaemon(false);
t.start();
}
} else if (_queueRunners.size() == numThreads) {

View File

@ -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,21 +1519,23 @@ 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());
else
_pingFile.delete();
}

View File

@ -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")))
if (getBooleanProperty("i2p.dummyClientFacade"))
System.err.println("i2p.dummpClientFacade currently unsupported");
_clientManagerFacade = new ClientManagerFacadeImpl(this);
else
_clientManagerFacade = new DummyClientManagerFacade(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;
}
}

View File

@ -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);

View File

@ -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}
*

View File

@ -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,17 +73,17 @@ public class ClientManager {
/** Todo: Start a 3rd listener for IPV6? */
private void startListeners(int port) {
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);
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);
Thread t = new I2PThread(_listener, "ClientListener:" + port, true);
t.start();
}
_isStarted = true;
}
public void restart() {
shutdown();
@ -95,9 +104,10 @@ public class ClientManager {
}
public void shutdown() {
_isStarted = false;
_log.info("Shutting down the ClientManager");
if (_listener != null)
_listener.stopListening();
_internalListener.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...

View File

@ -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");
}
}

View File

@ -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) {

View File

@ -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; }
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -433,8 +433,10 @@ public class NTCPTransport extends TransportImpl {
return skews;
}
private static final int NUM_CONCURRENT_READERS = 3;
private static final int NUM_CONCURRENT_WRITERS = 3;
private static final int MIN_CONCURRENT_READERS = 2; // unless < 32MB
private static final int MIN_CONCURRENT_WRITERS = 2; // unless < 32MB
private static final int MAX_CONCURRENT_READERS = 4;
private static final int MAX_CONCURRENT_WRITERS = 4;
/**
* Called by TransportManager.
@ -449,12 +451,8 @@ public class NTCPTransport extends TransportImpl {
if (_pumper.isAlive())
return _myAddress != null ? _myAddress.toRouterAddress() : null;
if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening");
_finisher.start();
_pumper.startPumping();
_reader.startReading(NUM_CONCURRENT_READERS);
_writer.startWriting(NUM_CONCURRENT_WRITERS);
startIt();
configureLocalAddress();
return bindAddress();
}
@ -471,12 +469,8 @@ public class NTCPTransport extends TransportImpl {
if (_pumper.isAlive())
return _myAddress != null ? _myAddress.toRouterAddress() : null;
if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening");
_finisher.start();
_pumper.startPumping();
_reader.startReading(NUM_CONCURRENT_READERS);
_writer.startWriting(NUM_CONCURRENT_WRITERS);
startIt();
if (addr == null)
_myAddress = null;
else
@ -484,6 +478,28 @@ public class NTCPTransport extends TransportImpl {
return bindAddress();
}
/**
* Start up. Caller must synchronize.
* @since 0.8.3
*/
private void startIt() {
_finisher.start();
_pumper.startPumping();
long maxMemory = Runtime.getRuntime().maxMemory();
int nr, nw;
if (maxMemory < 32*1024*1024) {
nr = nw = 1;
} else if (maxMemory < 64*1024*1024) {
nr = nw = 2;
} else {
nr = Math.max(MIN_CONCURRENT_READERS, Math.min(MAX_CONCURRENT_READERS, _context.bandwidthLimiter().getInboundKBytesPerSecond() / 20));
nw = Math.max(MIN_CONCURRENT_WRITERS, Math.min(MAX_CONCURRENT_WRITERS, _context.bandwidthLimiter().getOutboundKBytesPerSecond() / 20));
}
_reader.startReading(nr);
_writer.startWriting(nw);
}
public boolean isAlive() {
return _pumper.isAlive();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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());

View File

@ -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);
}

View File

@ -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 {