propagate from branch 'i2p.i2p' (head 3d405c867f6903bf1d69b04c1daebf3146882525)
to branch 'i2p.i2p.zzz.test4' (head bfd85b10fdd1542526a4b9c53e5d4a733087f317)
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>):
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
|
@ -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";
|
||||
|
@ -59,7 +59,13 @@ public class GraphHelper extends FormHandler {
|
||||
try { _width = Math.min(Integer.parseInt(str), MAX_X); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
public void setRefreshDelay(String str) {
|
||||
try { _refreshDelaySeconds = Math.max(Integer.parseInt(str), MIN_REFRESH); } catch (NumberFormatException nfe) {}
|
||||
try {
|
||||
int rds = Integer.parseInt(str);
|
||||
if (rds > 0)
|
||||
_refreshDelaySeconds = Math.max(rds, MIN_REFRESH);
|
||||
else
|
||||
_refreshDelaySeconds = -1;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
public String getImages() {
|
||||
@ -83,7 +89,7 @@ public class GraphHelper extends FormHandler {
|
||||
+ "&periodCount=" + (3 * _periodCount )
|
||||
+ "&width=" + (3 * _width)
|
||||
+ "&height=" + (3 * _height)
|
||||
+ "\" / target=\"_blank\">");
|
||||
+ "\" target=\"_blank\">");
|
||||
String title = _("Combined bandwidth graph");
|
||||
_out.write("<img class=\"statimage\" width=\""
|
||||
+ (_width + 83) + "\" height=\"" + (_height + 92)
|
||||
@ -129,6 +135,8 @@ public class GraphHelper extends FormHandler {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final int[] times = { 60, 2*60, 5*60, 10*60, 30*60, 60*60, -1 };
|
||||
|
||||
public String getForm() {
|
||||
String prev = System.getProperty("net.i2p.router.web.GraphHelper.nonce");
|
||||
if (prev != null) System.setProperty("net.i2p.router.web.GraphHelper.noncePrev", prev);
|
||||
@ -145,8 +153,22 @@ public class GraphHelper extends FormHandler {
|
||||
_out.write(_("Image sizes") + ": " + _("width") + ": <input size=\"4\" type=\"text\" name=\"width\" value=\"" + _width
|
||||
+ "\"> " + _("pixels") + ", " + _("height") + ": <input size=\"4\" type=\"text\" name=\"height\" value=\"" + _height
|
||||
+ "\"> " + _("pixels") + "<br>\n");
|
||||
_out.write(_("Refresh delay") + ": <select name=\"refreshDelay\"><option value=\"60\">1 " + _("minute") + "</option><option value=\"120\">2 " + _("minutes") + "</option><option value=\"300\">5 " + _("minutes") + "</option><option value=\"600\">10 " + _("minutes") + "</option><option value=\"1800\">30 " + _("minutes") + "</option><option value=\"3600\">1 " + _("hour") + "</option><option value=\"-1\">" + _("Never") + "</option></select><br>\n");
|
||||
_out.write("<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>");
|
||||
_out.write(_("Refresh delay") + ": <select name=\"refreshDelay\">");
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
_out.write("<option value=\"");
|
||||
_out.write(Integer.toString(times[i]));
|
||||
_out.write("\"");
|
||||
if (times[i] == _refreshDelaySeconds)
|
||||
_out.write(" selected=\"true\"");
|
||||
_out.write(">");
|
||||
if (times[i] > 0)
|
||||
_out.write(DataHelper.formatDuration2(times[i] * 1000));
|
||||
else
|
||||
_out.write(_("Never"));
|
||||
_out.write("</option>\n");
|
||||
}
|
||||
_out.write("</select><br>\n" +
|
||||
"<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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) {
|
||||
|
12
apps/routerconsole/jsp/viewhistory.jsp
Normal file
12
apps/routerconsole/jsp/viewhistory.jsp
Normal 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());
|
||||
%>
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
183
core/java/src/net/i2p/client/I2CPSSLSocketFactory.java
Normal file
183
core/java/src/net/i2p/client/I2CPSSLSocketFactory.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
51
core/java/src/net/i2p/internal/I2CPMessageQueue.java
Normal file
51
core/java/src/net/i2p/internal/I2CPMessageQueue.java
Normal 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());
|
||||
}
|
||||
}
|
19
core/java/src/net/i2p/internal/InternalClientManager.java
Normal file
19
core/java/src/net/i2p/internal/InternalClientManager.java
Normal 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;
|
||||
}
|
56
core/java/src/net/i2p/internal/PoisonI2CPMessage.java
Normal file
56
core/java/src/net/i2p/internal/PoisonI2CPMessage.java
Normal 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]";
|
||||
}
|
||||
}
|
62
core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java
Normal file
62
core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
core/java/src/net/i2p/internal/package.html
Normal file
7
core/java/src/net/i2p/internal/package.html
Normal 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>
|
@ -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");
|
||||
}
|
||||
*****/
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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")) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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")) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
*
|
||||
|
@ -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...
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user