forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.test2' (head 0feb2e6806927f68c7333aaa0892de185bb2629c)
to branch 'i2p.i2p' (head 0482fa843cb1e9d7ec281440056eef3a0ab07bdb)
This commit is contained in:
@@ -528,9 +528,9 @@ public class TrackerClient implements Runnable {
|
||||
!snark.isChecking() &&
|
||||
info.getSeedCount() > 100 &&
|
||||
coordinator.getPeerCount() <= 0 &&
|
||||
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
|
||||
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
|
||||
snark.getTotalLength() > 0 &&
|
||||
uploaded >= snark.getTotalLength() * 5 / 4) {
|
||||
uploaded >= snark.getTotalLength() / 2) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Auto stopping " + snark.getBaseName());
|
||||
snark.setAutoStoppable(false);
|
||||
|
@@ -21,6 +21,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.BigPipedInputStream;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.ReusableGZIPInputStream;
|
||||
|
||||
@@ -251,16 +252,27 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
//out.flush();
|
||||
PipedInputStream pi = BigPipedInputStream.getInstance();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
// 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.getClientExecutor().execute(new Pusher(pi, out));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
// shouldn't happen
|
||||
throw ree;
|
||||
}
|
||||
Runnable r = new Pusher(pi, out);
|
||||
out = po;
|
||||
// TODO we should be able to do this inline somehow
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
// 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 {
|
||||
tcg.getClientExecutor().execute(r);
|
||||
} catch (RejectedExecutionException ree) {
|
||||
// shouldn't happen
|
||||
throw ree;
|
||||
}
|
||||
} else {
|
||||
// Fallback in case TCG.getInstance() is null, never instantiated
|
||||
// and we were not started by TCG.
|
||||
// Maybe a plugin loaded before TCG? Should be rare.
|
||||
Thread t = new I2PAppThread(r, "Pusher");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
|
@@ -122,9 +122,11 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
int port = addr.getPort();
|
||||
i2ps = createI2PSocket(clientDest, port);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, null, null, mySockets,
|
||||
I2PTunnelRunner t = new I2PTunnelRunner(s, i2ps, sockLock, null, null, mySockets,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error connecting", ex);
|
||||
|
@@ -16,12 +16,8 @@ import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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 java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
@@ -77,18 +73,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
// true if we are chained from a server.
|
||||
private boolean chained;
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
private static volatile ThreadPoolExecutor _executor;
|
||||
private static int _executorThreadCount;
|
||||
private static final Object _executorLock = new Object();
|
||||
private volatile ThreadPoolExecutor _executor;
|
||||
|
||||
public static final String PROP_USE_SSL = I2PTunnelServer.PROP_USE_SSL;
|
||||
|
||||
@@ -116,11 +101,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
|
||||
Thread t = new I2PAppThread(this, "Client " + tunnel.listenHost + ':' + localPort);
|
||||
t.start();
|
||||
open = true;
|
||||
@@ -184,11 +164,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
|
||||
// normalize path so we can find it
|
||||
if (pkf != null) {
|
||||
File keyFile = new File(pkf);
|
||||
@@ -361,6 +336,16 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
return socketManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the shared client, so that on restart in android
|
||||
* we won't latch onto the old one
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
protected static synchronized void killSharedClient() {
|
||||
socketManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This may take a LONG time.
|
||||
*
|
||||
@@ -653,6 +638,16 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
_executor = tcg.getClientExecutor();
|
||||
} else {
|
||||
// Fallback in case TCG.getInstance() is null, never instantiated
|
||||
// and we were not started by TCG.
|
||||
// Maybe a plugin loaded before TCG? Should be rare.
|
||||
// Never shut down.
|
||||
_executor = new TunnelControllerGroup.CustomThreadPoolExecutor();
|
||||
}
|
||||
while (open) {
|
||||
Socket s = ss.accept();
|
||||
manageConnection(s);
|
||||
@@ -672,30 +667,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return may be null if no class has been instantiated
|
||||
* @since 0.8.8
|
||||
*/
|
||||
static ThreadPoolExecutor getClientExecutor() {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.8
|
||||
*/
|
||||
static void killClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor != null) {
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||
_executor.shutdownNow();
|
||||
_executor = null;
|
||||
}
|
||||
// kill the shared client, so that on restart in android
|
||||
// we won't latch onto the old one
|
||||
socketManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the connection just opened on the specified socket
|
||||
*
|
||||
@@ -721,26 +692,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Runnable>(), 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
|
||||
*/
|
||||
@@ -822,7 +773,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
/**
|
||||
* Manage a connection in a separate thread. This only works if
|
||||
* you do not override manageConnection()
|
||||
* you do not override manageConnection().
|
||||
*
|
||||
* This is run in a thread from an unlimited-size thread pool,
|
||||
* so it may block or run indefinitely.
|
||||
*/
|
||||
protected abstract void clientConnectionRun(Socket s);
|
||||
}
|
||||
|
@@ -292,7 +292,9 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
response = SUCCESS_RESPONSE;
|
||||
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (IOException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
|
@@ -972,7 +972,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
response = null;
|
||||
}
|
||||
Thread t = new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, response, onTimeout);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1091,6 +1093,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
sktOpts.setPort(remotePort);
|
||||
I2PSocket i2ps = createI2PSocket(clientDest, sktOpts);
|
||||
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
Thread t;
|
||||
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
|
||||
byte[] data;
|
||||
byte[] response;
|
||||
@@ -1101,13 +1104,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
data = null;
|
||||
response = SUCCESS_RESPONSE;
|
||||
}
|
||||
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
t.start();
|
||||
t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
|
||||
} else {
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
Thread t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
t.start();
|
||||
t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
}
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch(IOException ex) {
|
||||
if(_log.shouldLog(Log.INFO)) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
|
@@ -86,7 +86,8 @@ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
// ignore
|
||||
}
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// t2 = fromI2P now run inline
|
||||
//t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -302,16 +302,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
Runnable t;
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PAppThread req = new I2PAppThread(
|
||||
new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext(), _log),
|
||||
Thread.currentThread().getName()+".hc");
|
||||
req.start();
|
||||
t = new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext(), _log);
|
||||
} else {
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(),
|
||||
t = new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(),
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
}
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
|
@@ -136,8 +136,11 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
|
||||
DCCHelper dcc = _dccEnabled ? new DCC(s.getLocalAddress().getAddress()) : null;
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " out", true);
|
||||
out.start();
|
||||
//Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc), "IRC Client " + _clientId + " out", true);
|
||||
Runnable out = new IrcOutboundFilter(s,i2ps, expectedPong, _log, dcc);
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//out.start();
|
||||
out.run();
|
||||
} catch (Exception ex) {
|
||||
// generally NoRouteToHostException
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@@ -140,7 +140,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(),
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
// Send a response so the user doesn't just see a disconnect
|
||||
|
@@ -62,8 +62,6 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
private long totalSent;
|
||||
private long totalReceived;
|
||||
|
||||
private static final AtomicLong __forwarderId = new AtomicLong();
|
||||
|
||||
/**
|
||||
* For use in new constructor
|
||||
* @since 0.9.14
|
||||
@@ -268,9 +266,10 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
in = new BufferedInputStream(in, 2*NETWORK_BUFFER_SIZE);
|
||||
StreamForwarder toI2P = new StreamForwarder(in, i2pout, true);
|
||||
StreamForwarder fromI2P = new StreamForwarder(i2pin, out, false);
|
||||
// TODO can we run one of these inline and save a thread?
|
||||
toI2P.start();
|
||||
fromI2P.start();
|
||||
// We are already a thread, so run the second one inline
|
||||
//fromI2P.start();
|
||||
fromI2P.run();
|
||||
synchronized (finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
@@ -384,7 +383,8 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
// ignore
|
||||
}
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
// t2 = fromI2P now run inline
|
||||
//t2.join(30*1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,7 +426,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + '.' + __forwarderId.incrementAndGet());
|
||||
setName("StreamForwarder " + _runnerId + '.' + direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -80,6 +80,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
protected I2PTunnelTask task;
|
||||
protected boolean bidir;
|
||||
private ThreadPoolExecutor _executor;
|
||||
protected volatile ThreadPoolExecutor _clientExecutor;
|
||||
private final Map<Integer, InetSocketAddress> _socketMap = new ConcurrentHashMap<Integer, InetSocketAddress>(4);
|
||||
|
||||
/** unused? port should always be specified */
|
||||
@@ -470,6 +471,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
if (_usePool) {
|
||||
_executor = new CustomThreadPoolExecutor(getHandlerCount(), "ServerHandler pool " + remoteHost + ':' + remotePort);
|
||||
}
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
if (tcg != null) {
|
||||
_clientExecutor = tcg.getClientExecutor();
|
||||
} else {
|
||||
// Fallback in case TCG.getInstance() is null, never instantiated
|
||||
// and we were not started by TCG.
|
||||
// Maybe a plugin loaded before TCG? Should be rare.
|
||||
// Never shut down.
|
||||
_clientExecutor = new TunnelControllerGroup.CustomThreadPoolExecutor();
|
||||
}
|
||||
while (open) {
|
||||
try {
|
||||
I2PServerSocket ci2pss = i2pss;
|
||||
@@ -563,6 +574,17 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run in a thread from a limited-size thread pool via Handler.run(),
|
||||
* except for a standard server (this class, no extension, as determined in getUsePool()),
|
||||
* it is run directly in the acceptor thread (see run()).
|
||||
*
|
||||
* In either case, this method and any overrides must spawn a thread and return quickly.
|
||||
* If blocking while reading the headers (as in HTTP and IRC), the thread pool
|
||||
* may be exhausted.
|
||||
*
|
||||
* See PROP_USE_POOL, DEFAULT_USE_POOL, PROP_HANDLER_COUNT, DEFAULT_HANDLER_COUNT
|
||||
*/
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
||||
@@ -577,7 +599,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
afterSocket = getTunnel().getContext().clock().now();
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, null,
|
||||
null, (I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
|
@@ -425,7 +425,7 @@ public class TunnelController implements Logging {
|
||||
// We use _sessions AND the tunnel sessions as
|
||||
// _sessions will be null for delay-open tunnels - see acquire().
|
||||
// We want the current sessions.
|
||||
Set<I2PSession> sessions = new HashSet(_tunnel.getSessions());
|
||||
Set<I2PSession> sessions = new HashSet<I2PSession>(_tunnel.getSessions());
|
||||
if (_sessions != null)
|
||||
sessions.addAll(_sessions);
|
||||
return sessions;
|
||||
|
@@ -9,6 +9,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
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 java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.*;
|
||||
@@ -48,6 +55,21 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
*/
|
||||
private final Map<I2PSession, Set<TunnelController>> _sessions;
|
||||
|
||||
/**
|
||||
* We keep a 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.
|
||||
*
|
||||
* May also be used by servers, carefully,
|
||||
* as there is no limit on threads.
|
||||
*/
|
||||
private ThreadPoolExecutor _executor;
|
||||
private static final AtomicLong _executorThreadCount = new AtomicLong();
|
||||
private final Object _executorLock = new Object();
|
||||
/** how long to wait before dropping an idle thread */
|
||||
private static final long HANDLER_KEEPALIVE_MS = 2*60*1000;
|
||||
|
||||
|
||||
/**
|
||||
* In I2PAppContext will instantiate if necessary and always return non-null.
|
||||
* As of 0.9.4, when in RouterContext, will return null (except in Android)
|
||||
@@ -206,8 +228,7 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
if (_instance == this)
|
||||
_instance = null;
|
||||
}
|
||||
/// fixme static
|
||||
I2PTunnelClientBase.killClientExecutor();
|
||||
killClientExecutor();
|
||||
changeState(STOPPED);
|
||||
}
|
||||
|
||||
@@ -500,4 +521,59 @@ public class TunnelControllerGroup implements ClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-null
|
||||
* @since 0.8.8 Moved from I2PTunnelClientBase in 0.9.18
|
||||
*/
|
||||
ThreadPoolExecutor getClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor == null)
|
||||
_executor = new CustomThreadPoolExecutor();
|
||||
}
|
||||
return _executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.8 Moved from I2PTunnelClientBase in 0.9.18
|
||||
*/
|
||||
private void killClientExecutor() {
|
||||
synchronized (_executorLock) {
|
||||
if (_executor != null) {
|
||||
_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
|
||||
_executor.shutdownNow();
|
||||
_executor = null;
|
||||
}
|
||||
}
|
||||
// kill the shared client, so that on restart in android
|
||||
// we won't latch onto the old one
|
||||
I2PTunnelClientBase.killSharedClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not really needed for now but in case we want to add some hooks like afterExecute().
|
||||
* Package private for fallback in case TCG.getInstance() is null, never instantiated
|
||||
* but a plugin still needs it... should be rare.
|
||||
*
|
||||
* @since 0.9.18 Moved from I2PTunnelClientBase
|
||||
*/
|
||||
static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
public CustomThreadPoolExecutor() {
|
||||
super(0, Integer.MAX_VALUE, HANDLER_KEEPALIVE_MS, TimeUnit.MILLISECONDS,
|
||||
new SynchronousQueue<Runnable>(), new CustomThreadFactory());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just to set the name and set Daemon
|
||||
* @since 0.9.18 Moved from I2PTunnelClientBase
|
||||
*/
|
||||
private static class CustomThreadFactory implements ThreadFactory {
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread rv = Executors.defaultThreadFactory().newThread(r);
|
||||
rv.setName("I2PTunnel Client Runner " + _executorThreadCount.incrementAndGet());
|
||||
rv.setDaemon(true);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -76,7 +76,9 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase {
|
||||
try {
|
||||
i2ps = createI2PSocket(dest, opts);
|
||||
Thread t = new Runner(s, i2ps);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (Exception ex) {
|
||||
_log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex);
|
||||
closeSocket(s);
|
||||
|
@@ -111,7 +111,9 @@ public class I2PTunnelDCCServer extends I2PTunnelServer {
|
||||
_sockList.add(socket);
|
||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, null, _sockList,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// run in the unlimited client pool
|
||||
//t.start();
|
||||
_clientExecutor.execute(t);
|
||||
local.socket = socket;
|
||||
local.expire = getTunnel().getContext().clock().now() + OUTBOUND_EXPIRE;
|
||||
_active.put(Integer.valueOf(myPort), local);
|
||||
|
@@ -55,9 +55,12 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
"SOCKS IRC Client " + id + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
"SOCKS IRC Client " + id + " out", true);
|
||||
out.start();
|
||||
//Thread out = new I2PAppThread(new IrcOutboundFilter(clientSock, destSock, expectedPong, _log),
|
||||
// "SOCKS IRC Client " + id + " out", true);
|
||||
Runnable out = new IrcOutboundFilter(clientSock, destSock, expectedPong, _log);
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//out.start();
|
||||
out.run();
|
||||
} catch (SOCKSException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error from SOCKS connection", e);
|
||||
|
@@ -56,7 +56,9 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets,
|
||||
(I2PTunnelRunner.FailCallback) null);
|
||||
t.start();
|
||||
// we are called from an unlimited thread pool, so run inline
|
||||
//t.start();
|
||||
t.run();
|
||||
} catch (SOCKSException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error from SOCKS connection", e);
|
||||
|
@@ -148,6 +148,13 @@ public class NetDbRenderer {
|
||||
else
|
||||
buf.append(dest.toBase64().substring(0, 6));
|
||||
buf.append(")<br>\n");
|
||||
String b32 = dest.toBase32();
|
||||
buf.append("<a href=\"http://").append(b32).append("\">").append(b32).append("</a><br>\n");
|
||||
String host = _context.namingService().reverseLookup(dest);
|
||||
if (host == null) {
|
||||
buf.append("<a href=\"/susidns/addressbook.jsp?book=private&destination=")
|
||||
.append(dest.toBase64()).append("#add\">").append(_("Add to local addressbook")).append("</a><br>\n");
|
||||
}
|
||||
} else {
|
||||
buf.append(" (").append(_("Destination")).append(' ');
|
||||
String host = _context.namingService().reverseLookup(dest);
|
||||
|
BIN
apps/susimail/src/icons/drive_edit.png
Normal file
BIN
apps/susimail/src/icons/drive_edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 714 B |
@@ -88,6 +88,10 @@ class Mail {
|
||||
error = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* This may or may not contain the body also.
|
||||
* @return may be null
|
||||
*/
|
||||
public synchronized ReadBuffer getHeader() {
|
||||
return header;
|
||||
}
|
||||
@@ -103,6 +107,10 @@ class Mail {
|
||||
return header != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This contains the header also.
|
||||
* @return may be null
|
||||
*/
|
||||
public synchronized ReadBuffer getBody() {
|
||||
return body;
|
||||
}
|
||||
|
@@ -116,6 +116,7 @@ public class WebMail extends HttpServlet
|
||||
private static final String LOGOUT = "logout";
|
||||
private static final String RELOAD = "reload";
|
||||
private static final String SAVE = "save";
|
||||
private static final String SAVE_AS = "saveas";
|
||||
private static final String REFRESH = "refresh";
|
||||
private static final String CONFIGURE = "configure";
|
||||
private static final String NEW = "new";
|
||||
@@ -1298,6 +1299,33 @@ public class WebMail extends HttpServlet
|
||||
return isRaw;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process save-as link in message view
|
||||
*
|
||||
* @param sessionObject
|
||||
* @param request
|
||||
* @return If true, we sent the file or 404, do not send any other response
|
||||
* @since 0.9.18
|
||||
*/
|
||||
private static boolean processSaveAsLink(SessionObject sessionObject, RequestWrapper request, HttpServletResponse response)
|
||||
{
|
||||
String str = request.getParameter(SAVE_AS);
|
||||
if( str == null )
|
||||
return false;
|
||||
Mail mail = sessionObject.mailCache.getMail( sessionObject.showUIDL, MailCache.FetchMode.ALL );
|
||||
if( mail != null ) {
|
||||
if (sendMailSaveAs(sessionObject, mail, response))
|
||||
return true;
|
||||
}
|
||||
// error if we get here
|
||||
sessionObject.error += _("Message not found.");
|
||||
try {
|
||||
response.sendError(404, _("Message not found."));
|
||||
} catch (IOException ioe) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hashCode
|
||||
* @return the part or null
|
||||
@@ -1631,6 +1659,10 @@ public class WebMail extends HttpServlet
|
||||
// download or raw view sent, or 404
|
||||
return;
|
||||
}
|
||||
if (processSaveAsLink(sessionObject, request, response)) {
|
||||
// download or sent, or 404
|
||||
return;
|
||||
}
|
||||
// If the last message has just been deleted then
|
||||
// sessionObject.state = STATE_LIST and
|
||||
// sessionObject.showUIDL = null
|
||||
@@ -1790,7 +1822,7 @@ public class WebMail extends HttpServlet
|
||||
name = part.name;
|
||||
else
|
||||
name = "part" + part.hashCode();
|
||||
String name2 = name.replace( "\\.", "_" );
|
||||
String name2 = sanitizeFilename(name);
|
||||
response.setContentType( "application/zip; name=\"" + name2 + ".zip\"" );
|
||||
response.addHeader( "Content-Disposition:", "attachment; filename=\"" + name2 + ".zip\"" );
|
||||
ZipEntry entry = new ZipEntry( name );
|
||||
@@ -1809,6 +1841,54 @@ public class WebMail extends HttpServlet
|
||||
}
|
||||
return shown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the mail to be saved by the browser
|
||||
*
|
||||
* @param sessionObject
|
||||
* @param response
|
||||
* @return success
|
||||
* @since 0.9.18
|
||||
*/
|
||||
private static boolean sendMailSaveAs(SessionObject sessionObject, Mail mail,
|
||||
HttpServletResponse response)
|
||||
{
|
||||
ReadBuffer content = mail.getBody();
|
||||
|
||||
if(content == null)
|
||||
return false;
|
||||
String name = mail.subject != null ? sanitizeFilename(mail.subject) : "message";
|
||||
try {
|
||||
response.setContentType("message/rfc822");
|
||||
response.setContentLength(content.length);
|
||||
// cache-control?
|
||||
response.addHeader( "Content-Disposition:", "attachment; filename=\"" + name + ".eml\"" );
|
||||
response.getOutputStream().write(content.content, content.offset, content.length);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the UTF-8 to ISO-8859-1 suitable for inclusion in a header.
|
||||
* This will result in a bunch of ??? for non-Western languages.
|
||||
*
|
||||
* @param sessionObject
|
||||
* @param response
|
||||
* @return success
|
||||
* @since 0.9.18
|
||||
*/
|
||||
private static String sanitizeFilename(String name) {
|
||||
try {
|
||||
name = new String(name.getBytes("ISO-8859-1"), "ISO-8859-1");
|
||||
} catch( UnsupportedEncodingException uee ) {}
|
||||
// strip control chars?
|
||||
name = name.replace('"', '_');
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sessionObject
|
||||
* @param request
|
||||
@@ -2255,7 +2335,8 @@ public class WebMail extends HttpServlet
|
||||
out.println( button( NEW, _("New") ) + spacer +
|
||||
button( REPLY, _("Reply") ) +
|
||||
button( REPLYALL, _("Reply All") ) +
|
||||
button( FORWARD, _("Forward") ) + spacer);
|
||||
button( FORWARD, _("Forward") ) + spacer +
|
||||
button( SAVE_AS, _("Save As") ) + spacer);
|
||||
if (sessionObject.reallyDelete)
|
||||
out.println(button2(DELETE, _("Delete")));
|
||||
else
|
||||
|
@@ -108,6 +108,9 @@ public class DataHelper {
|
||||
* for the value. Finally after that comes the literal UTF-8 character ';'. This key=value;
|
||||
* is repeated until there are no more bytes (not characters!) left as defined by the
|
||||
* first two byte integer.
|
||||
*
|
||||
* As of 0.9.18, throws DataFormatException on duplicate key
|
||||
*
|
||||
* @param rawStream stream to read the mapping from
|
||||
* @throws DataFormatException if the format is invalid
|
||||
* @throws IOException if there is a problem reading the data
|
||||
@@ -122,7 +125,14 @@ public class DataHelper {
|
||||
|
||||
/**
|
||||
* Ditto, load into an existing properties
|
||||
*
|
||||
* As of 0.9.18, throws DataFormatException on duplicate key
|
||||
*
|
||||
* @param props the Properties to load into
|
||||
* @param rawStream stream to read the mapping from
|
||||
* @throws DataFormatException if the format is invalid
|
||||
* @throws IOException if there is a problem reading the data
|
||||
* @return the parameter props
|
||||
* @since 0.8.13
|
||||
*/
|
||||
public static Properties readProperties(InputStream rawStream, Properties props)
|
||||
@@ -148,7 +158,9 @@ public class DataHelper {
|
||||
if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) {
|
||||
throw new DataFormatException("Bad value");
|
||||
}
|
||||
props.put(key, val);
|
||||
Object old = props.put(key, val);
|
||||
if (old != null)
|
||||
throw new DataFormatException("Duplicate key " + key);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
@@ -299,6 +311,8 @@ public class DataHelper {
|
||||
* Warning - confusing method name, Properties is the target.
|
||||
* Strings must be UTF-8 encoded in the byte array.
|
||||
*
|
||||
* As of 0.9.18, throws DataFormatException on duplicate key
|
||||
*
|
||||
* @param source source
|
||||
* @param target returned Properties
|
||||
* @return new offset
|
||||
@@ -333,7 +347,9 @@ public class DataHelper {
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("Bad value", ioe);
|
||||
}
|
||||
target.put(key, val);
|
||||
Object old= target.put(key, val);
|
||||
if (old != null)
|
||||
throw new DataFormatException("Duplicate key " + key);
|
||||
}
|
||||
return offset + size;
|
||||
}
|
||||
@@ -398,6 +414,9 @@ public class DataHelper {
|
||||
* - '=' is the only key-termination character (not ':' or whitespace)
|
||||
*
|
||||
* As of 0.9.10, an empty value is allowed.
|
||||
*
|
||||
* As in Java Properties, duplicate keys are allowed, last one wins.
|
||||
*
|
||||
*/
|
||||
public static void loadProps(Properties props, File file) throws IOException {
|
||||
loadProps(props, file, false);
|
||||
|
@@ -61,6 +61,8 @@ public class LogManager {
|
||||
private static final String PROP_DROP = "logger.dropOnOverflow";
|
||||
/** @since 0.9.3 */
|
||||
private static final String PROP_DUP = "logger.dropDuplicates";
|
||||
/** @since 0.9.18 */
|
||||
private static final String PROP_FLUSH = "logger.flushInterval";
|
||||
public final static String PROP_RECORD_PREFIX = "logger.record.";
|
||||
|
||||
public final static String DEFAULT_FORMAT = DATE + " " + PRIORITY + " [" + THREAD + "] " + CLASS + ": " + MESSAGE;
|
||||
@@ -125,6 +127,8 @@ public class LogManager {
|
||||
private boolean _dropOnOverflow;
|
||||
private boolean _dropDuplicates;
|
||||
private final AtomicLong _droppedRecords = new AtomicLong();
|
||||
// in seconds
|
||||
private int _flushInterval = (int) (LogWriter.FLUSH_INTERVAL / 1000);
|
||||
|
||||
private boolean _alreadyNoticedMissingConfig;
|
||||
|
||||
@@ -160,6 +164,7 @@ public class LogManager {
|
||||
if (_writer != null)
|
||||
return;
|
||||
_writer = new LogWriter(this);
|
||||
_writer.setFlushInterval(_flushInterval * 1000);
|
||||
// if you enable logging in I2PThread again, you MUST change this back to Thread
|
||||
Thread t = new I2PThread(_writer, "LogWriter");
|
||||
t.setDaemon(true);
|
||||
@@ -269,6 +274,10 @@ public class LogManager {
|
||||
try {
|
||||
_records.put(record);
|
||||
} catch (InterruptedException ie) {}
|
||||
} else if (_flushInterval <= 0) {
|
||||
synchronized (_writer) {
|
||||
_writer.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +393,17 @@ public class LogManager {
|
||||
_logBufferSize = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
|
||||
try {
|
||||
String str = config.getProperty(PROP_FLUSH);
|
||||
if (str != null) {
|
||||
_flushInterval = Integer.parseInt(str);
|
||||
synchronized(this) {
|
||||
if (_writer != null)
|
||||
_writer.setFlushInterval(_flushInterval * 1000);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
|
||||
_dropOnOverflow = Boolean.parseBoolean(config.getProperty(PROP_DROP));
|
||||
String str = config.getProperty(PROP_DUP);
|
||||
_dropDuplicates = str == null || Boolean.parseBoolean(str);
|
||||
@@ -647,6 +667,7 @@ public class LogManager {
|
||||
rv.setProperty(PROP_DEFAULTLEVEL, Log.toLevelString(_defaultLimit));
|
||||
rv.setProperty(PROP_DISPLAYONSCREENLEVEL, Log.toLevelString(_onScreenLimit));
|
||||
rv.setProperty(PROP_CONSOLEBUFFERSIZE, Integer.toString(_consoleBufferSize));
|
||||
rv.setProperty(PROP_FLUSH, Integer.toString(_flushInterval));
|
||||
|
||||
for (LogLimit lim : _limits) {
|
||||
rv.setProperty(PROP_RECORD_PREFIX + lim.getRootName(), Log.toLevelString(lim.getLimit()));
|
||||
|
@@ -25,7 +25,9 @@ import java.util.Queue;
|
||||
class LogWriter implements Runnable {
|
||||
/** every 10 seconds? why? Just have the gui force a reread after a change?? */
|
||||
private final static long CONFIG_READ_INTERVAL = 50 * 1000;
|
||||
private final static long FLUSH_INTERVAL = 29 * 1000;
|
||||
final static long FLUSH_INTERVAL = 29 * 1000;
|
||||
private final static long MIN_FLUSH_INTERVAL = 2*1000;
|
||||
private final static long MAX_FLUSH_INTERVAL = 5*60*1000;
|
||||
private long _lastReadConfig;
|
||||
private long _numBytesInCurrentFile;
|
||||
// volatile as it changes on log file rotation
|
||||
@@ -38,6 +40,8 @@ class LogWriter implements Runnable {
|
||||
private static final int MAX_DISKFULL_MESSAGES = 8;
|
||||
private int _diskFullMessageCount;
|
||||
private LogRecord _last;
|
||||
// ms
|
||||
private volatile long _flushInterval = FLUSH_INTERVAL;
|
||||
|
||||
public LogWriter(LogManager manager) {
|
||||
_manager = manager;
|
||||
@@ -47,6 +51,14 @@ class LogWriter implements Runnable {
|
||||
public void stopWriting() {
|
||||
_write = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ms
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public void setFlushInterval(long interval) {
|
||||
_flushInterval = Math.min(MAX_FLUSH_INTERVAL, Math.max(MIN_FLUSH_INTERVAL, interval));
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_write = true;
|
||||
@@ -109,7 +121,7 @@ class LogWriter implements Runnable {
|
||||
if (shouldWait) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
this.wait(FLUSH_INTERVAL);
|
||||
this.wait(_flushInterval);
|
||||
}
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
# If you have a 'split' directory installation, with configuration
|
||||
# files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to
|
||||
# edit the file in the configuration directory, NOT the install directory.
|
||||
# When running as a Linux daemon, the configuration directory is /var/lib/i2p
|
||||
# and the install directory is /usr/share/i2p .
|
||||
#
|
||||
# Blocking is now enabled by default.
|
||||
# To disable blocking, set router.blocklist.enable=false on configadvanced.jsp,
|
||||
|
@@ -3,6 +3,8 @@
|
||||
# If you have a 'split' directory installation, with configuration
|
||||
# files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to
|
||||
# edit the file in the configuration directory, NOT the install directory.
|
||||
# When running as a Linux daemon, the configuration directory is /var/lib/i2p
|
||||
# and the install directory is /usr/share/i2p .
|
||||
#
|
||||
|
||||
# fire up the web console
|
||||
|
@@ -4,6 +4,8 @@
|
||||
# If you have a 'split' directory installation, with configuration
|
||||
# files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to
|
||||
# edit the file in the configuration directory, NOT the install directory.
|
||||
# When running as a Linux daemon, the configuration directory is /var/lib/i2p
|
||||
# and the install directory is /usr/share/i2p .
|
||||
#
|
||||
-->
|
||||
<head>
|
||||
|
@@ -32,6 +32,8 @@
|
||||
<!-- If you have a 'split' directory installation, with configuration -->
|
||||
<!-- files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to -->
|
||||
<!-- edit the file in the configuration directory, NOT the install directory. -->
|
||||
<!-- When running as a Linux daemon, the configuration directory is -->
|
||||
<!-- /var/lib/i2p and the install directory is /usr/share/i2p . -->
|
||||
<!-- -->
|
||||
<!-- ========================================================================= -->
|
||||
|
||||
|
@@ -3,6 +3,8 @@
|
||||
# If you have a 'split' directory installation, with configuration
|
||||
# files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to
|
||||
# edit the file in the configuration directory, NOT the install directory.
|
||||
# When running as a Linux daemon, the configuration directory is /var/lib/i2p
|
||||
# and the install directory is /usr/share/i2p .
|
||||
#
|
||||
|
||||
# eepproxy
|
||||
|
@@ -252,6 +252,11 @@ input.configure {
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
input.saveas {
|
||||
background: #000 url('/susimail/icons/drive_edit.png') no-repeat 2px center;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
input[type=file], input.new_upload {
|
||||
background: #000 url('/themes/console/images/add.png') no-repeat 2px center;
|
||||
min-height: 22px;
|
||||
|
@@ -274,6 +274,12 @@ input.configure {
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
input.saveas {
|
||||
background: #ddf url('/susimail/icons/drive_edit.png') no-repeat 4px center;
|
||||
padding: 2px 3px 2px 24px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
input[type=file], input.new_upload {
|
||||
background: #ddf url('/themes/console/images/add.png') no-repeat 4px center;
|
||||
padding: 2px 3px 2px 24px;
|
||||
|
@@ -2,6 +2,7 @@ package net.i2p.data.i2np;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
@@ -9,7 +10,8 @@ import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Hold the tunnel request record, managing its ElGamal encryption and decryption.
|
||||
* Holds the unencrypted 222-byte tunnel request record,
|
||||
* with a constructor for ElGamal decryption and a method for ElGamal encryption.
|
||||
* Iterative AES encryption/decryption is done elsewhere.
|
||||
*
|
||||
* Cleartext:
|
||||
@@ -36,7 +38,7 @@ import net.i2p.data.SessionKey;
|
||||
*
|
||||
*/
|
||||
public class BuildRequestRecord {
|
||||
private ByteArray _data;
|
||||
private final byte[] _data;
|
||||
|
||||
/**
|
||||
* If set in the flag byte, any peer may send a message into this tunnel, but if
|
||||
@@ -55,11 +57,10 @@ public class BuildRequestRecord {
|
||||
/** we show 16 bytes of the peer hash outside the elGamal block */
|
||||
public static final int PEER_SIZE = 16;
|
||||
|
||||
public BuildRequestRecord(ByteArray data) { _data = data; }
|
||||
public BuildRequestRecord() { }
|
||||
|
||||
public ByteArray getData() { return _data; }
|
||||
public void setData(ByteArray data) { _data = data; }
|
||||
/**
|
||||
* @return 222 bytes, non-null
|
||||
*/
|
||||
public byte[] getData() { return _data; }
|
||||
|
||||
private static final int OFF_RECV_TUNNEL = 0;
|
||||
private static final int OFF_OUR_IDENT = OFF_RECV_TUNNEL + 4;
|
||||
@@ -72,91 +73,101 @@ public class BuildRequestRecord {
|
||||
private static final int OFF_FLAG = OFF_REPLY_IV + IV_SIZE;
|
||||
private static final int OFF_REQ_TIME = OFF_FLAG + 1;
|
||||
private static final int OFF_SEND_MSG_ID = OFF_REQ_TIME + 4;
|
||||
private static final int PADDING_SIZE = 29;
|
||||
// 222
|
||||
private static final int LENGTH = OFF_SEND_MSG_ID + 4 + PADDING_SIZE;
|
||||
|
||||
|
||||
/** what tunnel ID should this receive messages on */
|
||||
public long readReceiveTunnelId() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_RECV_TUNNEL, 4);
|
||||
}
|
||||
/** true if the identity they expect us to be is who we are */
|
||||
public boolean readOurIdentityMatches(Hash ourIdentity) {
|
||||
return DataHelper.eq(ourIdentity.getData(), 0, _data.getData(), _data.getOffset() + OFF_OUR_IDENT, Hash.HASH_LENGTH);
|
||||
return DataHelper.fromLong(_data, OFF_RECV_TUNNEL, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* What tunnel ID the next hop receives messages on. If this is the outbound tunnel endpoint,
|
||||
* this specifies the tunnel ID to which the reply should be sent.
|
||||
*/
|
||||
public long readNextTunnelId() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_SEND_TUNNEL, 4);
|
||||
return DataHelper.fromLong(_data, OFF_SEND_TUNNEL, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next hop from the record. If this is the outbound tunnel endpoint, this specifies
|
||||
* the gateway to which the reply should be sent.
|
||||
*/
|
||||
public Hash readNextIdentity() {
|
||||
//byte rv[] = new byte[Hash.HASH_LENGTH];
|
||||
//System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH);
|
||||
//System.arraycopy(_data, OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH);
|
||||
//return new Hash(rv);
|
||||
return Hash.create(_data.getData(), _data.getOffset() + OFF_SEND_IDENT);
|
||||
return Hash.create(_data, OFF_SEND_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tunnel layer encryption key that the current hop should use
|
||||
*/
|
||||
public SessionKey readLayerKey() {
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
System.arraycopy(_data.getData(), _data.getOffset() + OFF_LAYER_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
System.arraycopy(_data, OFF_LAYER_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
return new SessionKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tunnel IV encryption key that the current hop should use
|
||||
*/
|
||||
public SessionKey readIVKey() {
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
System.arraycopy(_data.getData(), _data.getOffset() + OFF_IV_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
System.arraycopy(_data, OFF_IV_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
return new SessionKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Session key that should be used to encrypt the reply
|
||||
*/
|
||||
public SessionKey readReplyKey() {
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
System.arraycopy(_data.getData(), _data.getOffset() + OFF_REPLY_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
System.arraycopy(_data, OFF_REPLY_KEY, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
return new SessionKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* IV that should be used to encrypt the reply
|
||||
*/
|
||||
public byte[] readReplyIV() {
|
||||
byte iv[] = new byte[IV_SIZE];
|
||||
System.arraycopy(_data.getData(), _data.getOffset() + OFF_REPLY_IV, iv, 0, IV_SIZE);
|
||||
System.arraycopy(_data, OFF_REPLY_IV, iv, 0, IV_SIZE);
|
||||
return iv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current hop is the inbound gateway. If this is true, it means anyone can send messages to
|
||||
* this tunnel, but if it is false, only the current predecessor can.
|
||||
*
|
||||
*/
|
||||
public boolean readIsInboundGateway() {
|
||||
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_UNRESTRICTED_PREV) != 0;
|
||||
return (_data[OFF_FLAG] & FLAG_UNRESTRICTED_PREV) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current hop is the outbound endpoint. If this is true, the next identity and next tunnel
|
||||
* fields refer to where the reply should be sent.
|
||||
*/
|
||||
public boolean readIsOutboundEndpoint() {
|
||||
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
|
||||
return (_data[OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time that the request was sent (ms), truncated to the nearest hour
|
||||
*/
|
||||
public long readRequestTime() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
|
||||
return DataHelper.fromLong(_data, OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
|
||||
* this specifies the message ID with which the reply should be sent.
|
||||
*/
|
||||
public long readReplyMessageId() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_SEND_MSG_ID, 4);
|
||||
return DataHelper.fromLong(_data, OFF_SEND_MSG_ID, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,42 +175,43 @@ public class BuildRequestRecord {
|
||||
* bytes 0-15: truncated SHA-256 of the current hop's identity (the toPeer parameter)
|
||||
* bytes 15-527: ElGamal-2048 encrypted block
|
||||
* </pre>
|
||||
*
|
||||
* @return non-null
|
||||
*/
|
||||
public void encryptRecord(I2PAppContext ctx, PublicKey toKey, Hash toPeer, byte out[], int outOffset) {
|
||||
System.arraycopy(toPeer.getData(), 0, out, outOffset, PEER_SIZE);
|
||||
byte preEncr[] = new byte[OFF_SEND_MSG_ID + 4 + PADDING_SIZE];
|
||||
System.arraycopy(_data.getData(), _data.getOffset(), preEncr, 0, preEncr.length);
|
||||
byte encrypted[] = ctx.elGamalEngine().encrypt(preEncr, toKey);
|
||||
public EncryptedBuildRecord encryptRecord(I2PAppContext ctx, PublicKey toKey, Hash toPeer) {
|
||||
byte[] out = new byte[EncryptedBuildRecord.LENGTH];
|
||||
System.arraycopy(toPeer.getData(), 0, out, 0, PEER_SIZE);
|
||||
byte encrypted[] = ctx.elGamalEngine().encrypt(_data, toKey);
|
||||
// the elg engine formats it kind of weird, giving 257 bytes for each part rather than 256, so
|
||||
// we want to strip out that excess byte and store it in the record
|
||||
System.arraycopy(encrypted, 1, out, outOffset + PEER_SIZE, 256);
|
||||
System.arraycopy(encrypted, 258, out, outOffset + 256 + PEER_SIZE, 256);
|
||||
System.arraycopy(encrypted, 1, out, PEER_SIZE, 256);
|
||||
System.arraycopy(encrypted, 258, out, 256 + PEER_SIZE, 256);
|
||||
return new EncryptedBuildRecord(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the data from the specified record, writing the decrypted record into this instance's
|
||||
* buffer (but not overwriting the array contained within the old buffer)
|
||||
* data buffer
|
||||
*
|
||||
* Caller MUST check that first 16 bytes of our hash matches first 16 bytes of encryptedRecord
|
||||
* before calling this. Not checked here.
|
||||
*
|
||||
* @throws DataFormatException on decrypt fail
|
||||
* @since 0.9.18, was decryptRecord()
|
||||
*/
|
||||
public boolean decryptRecord(I2PAppContext ctx, PrivateKey ourKey, Hash ourIdent, ByteArray encryptedRecord) {
|
||||
if (DataHelper.eq(ourIdent.getData(), 0, encryptedRecord.getData(), encryptedRecord.getOffset(), PEER_SIZE)) {
|
||||
public BuildRequestRecord(I2PAppContext ctx, PrivateKey ourKey,
|
||||
EncryptedBuildRecord encryptedRecord) throws DataFormatException {
|
||||
byte preDecrypt[] = new byte[514];
|
||||
System.arraycopy(encryptedRecord.getData(), encryptedRecord.getOffset() + PEER_SIZE, preDecrypt, 1, 256);
|
||||
System.arraycopy(encryptedRecord.getData(), encryptedRecord.getOffset() + PEER_SIZE + 256, preDecrypt, 258, 256);
|
||||
System.arraycopy(encryptedRecord.getData(), PEER_SIZE, preDecrypt, 1, 256);
|
||||
System.arraycopy(encryptedRecord.getData(), PEER_SIZE + 256, preDecrypt, 258, 256);
|
||||
byte decrypted[] = ctx.elGamalEngine().decrypt(preDecrypt, ourKey);
|
||||
if (decrypted != null) {
|
||||
_data = new ByteArray(decrypted);
|
||||
_data.setOffset(0);
|
||||
return true;
|
||||
_data = decrypted;
|
||||
} else {
|
||||
return false;
|
||||
throw new DataFormatException("decrypt fail");
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int PADDING_SIZE = 29;
|
||||
|
||||
/**
|
||||
* Populate this instance with data. A new buffer is created to contain the data, with the
|
||||
* necessary randomized padding.
|
||||
@@ -215,14 +227,13 @@ public class BuildRequestRecord {
|
||||
* @param iv iv to be used when encrypting the reply to this build request
|
||||
* @param isInGateway are we the gateway of an inbound tunnel?
|
||||
* @param isOutEndpoint are we the endpoint of an outbound tunnel?
|
||||
* @since 0.9.18, was createRecord()
|
||||
*/
|
||||
public void createRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, long nextMsgId,
|
||||
public BuildRequestRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, long nextMsgId,
|
||||
SessionKey layerKey, SessionKey ivKey, SessionKey replyKey, byte iv[], boolean isInGateway,
|
||||
boolean isOutEndpoint) {
|
||||
if ( (_data == null) || (_data.getData() != null) )
|
||||
_data = new ByteArray();
|
||||
byte buf[] = new byte[OFF_SEND_MSG_ID+4+PADDING_SIZE];
|
||||
_data.setData(buf);
|
||||
byte buf[] = new byte[LENGTH];
|
||||
_data = buf;
|
||||
|
||||
/* bytes 0-3: tunnel ID to receive messages as
|
||||
* bytes 4-35: local router identity hash
|
||||
|
@@ -7,12 +7,17 @@ import net.i2p.data.SessionKey;
|
||||
//import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Read and write the reply to a tunnel build message record.
|
||||
* Class that creates an encrypted tunnel build message record.
|
||||
*
|
||||
* The reply record is the same size as the request record (528 bytes).
|
||||
*
|
||||
* When decrypted:
|
||||
*
|
||||
*<pre>
|
||||
* Bytes 0-31 contain the hash of bytes 32-527
|
||||
* Bytes 32-526 contain random data.
|
||||
* Byte 527 contains the reply.
|
||||
*</pre>
|
||||
*/
|
||||
public class BuildResponseRecord {
|
||||
|
||||
@@ -20,10 +25,12 @@ public class BuildResponseRecord {
|
||||
* Create a new encrypted response
|
||||
*
|
||||
* @param status the response 0-255
|
||||
* @param replyIV 16 bytes
|
||||
* @param responseMessageId unused except for debugging
|
||||
* @return a 528-byte response record
|
||||
*/
|
||||
public static byte[] create(I2PAppContext ctx, int status, SessionKey replyKey, byte replyIV[], long responseMessageId) {
|
||||
public static EncryptedBuildRecord create(I2PAppContext ctx, int status, SessionKey replyKey,
|
||||
byte replyIV[], long responseMessageId) {
|
||||
//Log log = ctx.logManager().getLog(BuildResponseRecord.class);
|
||||
byte rv[] = new byte[TunnelBuildReplyMessage.RECORD_SIZE];
|
||||
ctx.random().nextBytes(rv, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE - Hash.HASH_LENGTH - 1);
|
||||
@@ -35,6 +42,6 @@ public class BuildResponseRecord {
|
||||
ctx.aes().encrypt(rv, 0, rv, 0, replyKey, replyIV, rv.length);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug(responseMessageId + ": after encrypt: " + Base64.encode(rv, 0, 128));
|
||||
return rv;
|
||||
return new EncryptedBuildRecord(rv);
|
||||
}
|
||||
}
|
||||
|
@@ -214,9 +214,8 @@ public class DatabaseLookupMessage extends FastI2NPMessageImpl {
|
||||
public static boolean supportsEncryptedReplies(RouterInfo to) {
|
||||
if (to == null)
|
||||
return false;
|
||||
String v = to.getOption("router.version");
|
||||
return v != null &&
|
||||
VersionComparator.comp(v, MIN_ENCRYPTION_VERSION) >= 0;
|
||||
String v = to.getVersion();
|
||||
return VersionComparator.comp(v, MIN_ENCRYPTION_VERSION) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -105,7 +105,8 @@ public class DatabaseStoreMessage extends FastI2NPMessageImpl {
|
||||
_key = Hash.create(data, curIndex);
|
||||
curIndex += Hash.HASH_LENGTH;
|
||||
|
||||
type = (int)DataHelper.fromLong(data, curIndex, 1);
|
||||
// as of 0.9.18, ignore other 7 bits of the type byte, in preparation for future options
|
||||
int dbType = data[curIndex] & 0x01;
|
||||
curIndex++;
|
||||
|
||||
_replyToken = DataHelper.fromLong(data, curIndex, 4);
|
||||
@@ -124,7 +125,7 @@ public class DatabaseStoreMessage extends FastI2NPMessageImpl {
|
||||
_replyGateway = null;
|
||||
}
|
||||
|
||||
if (type == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
if (dbType == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
_dbEntry = new LeaseSet();
|
||||
try {
|
||||
_dbEntry.readBytes(new ByteArrayInputStream(data, curIndex, data.length-curIndex));
|
||||
@@ -133,7 +134,7 @@ public class DatabaseStoreMessage extends FastI2NPMessageImpl {
|
||||
} catch (IOException ioe) {
|
||||
throw new I2NPMessageException("Error reading the leaseSet", ioe);
|
||||
}
|
||||
} else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
|
||||
} else { // dbType == DatabaseEntry.KEY_TYPE_ROUTERINFO
|
||||
_dbEntry = new RouterInfo();
|
||||
int compressedSize = (int)DataHelper.fromLong(data, curIndex, 2);
|
||||
curIndex += 2;
|
||||
@@ -154,8 +155,6 @@ public class DatabaseStoreMessage extends FastI2NPMessageImpl {
|
||||
} catch (IOException ioe) {
|
||||
throw new I2NPMessageException("Corrupt compressed routerInfo size = " + compressedSize, ioe);
|
||||
}
|
||||
} else {
|
||||
throw new I2NPMessageException("Invalid type of key read from the structure - " + type);
|
||||
}
|
||||
//if (!key.equals(_dbEntry.getHash()))
|
||||
// throw new I2NPMessageException("Hash mismatch in DSM");
|
||||
|
32
router/java/src/net/i2p/data/i2np/EncryptedBuildRecord.java
Normal file
32
router/java/src/net/i2p/data/i2np/EncryptedBuildRecord.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.i2p.data.i2np;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* No warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
* ElGamal-encrypted request or response.
|
||||
* 528 bytes. Previously stored in a ByteArray.
|
||||
* May or may not be AES layer-encrypted.
|
||||
*
|
||||
* Note that these are layer-encrypted and layer-decrypted in-place.
|
||||
* Do not cache.
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public class EncryptedBuildRecord extends SimpleDataStructure {
|
||||
|
||||
public final static int LENGTH = TunnelBuildMessageBase.RECORD_SIZE;
|
||||
|
||||
/** @throws IllegalArgumentException if data is not correct length (null is ok) */
|
||||
public EncryptedBuildRecord(byte data[]) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return LENGTH;
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package net.i2p.data.i2np;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
|
||||
/**
|
||||
* Base for TBM, TBRM, VTBM, VTBRM
|
||||
@@ -18,7 +17,7 @@ import net.i2p.data.ByteArray;
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
|
||||
protected ByteArray _records[];
|
||||
protected EncryptedBuildRecord _records[];
|
||||
protected int RECORD_COUNT;
|
||||
public static final int MAX_RECORD_COUNT = 8;
|
||||
|
||||
@@ -31,14 +30,14 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
|
||||
super(context);
|
||||
if (records > 0) {
|
||||
RECORD_COUNT = records;
|
||||
_records = new ByteArray[records];
|
||||
_records = new EncryptedBuildRecord[records];
|
||||
}
|
||||
// else will be initialized by readMessage()
|
||||
}
|
||||
|
||||
public void setRecord(int index, ByteArray record) { _records[index] = record; }
|
||||
public void setRecord(int index, EncryptedBuildRecord record) { _records[index] = record; }
|
||||
|
||||
public ByteArray getRecord(int index) { return _records[index]; }
|
||||
public EncryptedBuildRecord getRecord(int index) { return _records[index]; }
|
||||
|
||||
/** @since 0.7.12 */
|
||||
public int getRecordCount() { return RECORD_COUNT; }
|
||||
@@ -57,7 +56,7 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
|
||||
int off = offset + (i * RECORD_SIZE);
|
||||
byte rec[] = new byte[RECORD_SIZE];
|
||||
System.arraycopy(data, off, rec, 0, RECORD_SIZE);
|
||||
setRecord(i, new ByteArray(rec));
|
||||
setRecord(i, new EncryptedBuildRecord(rec));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
|
||||
if (remaining < 0)
|
||||
throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");
|
||||
for (int i = 0; i < RECORD_COUNT; i++) {
|
||||
System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE);
|
||||
System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE);
|
||||
curIndex += RECORD_SIZE;
|
||||
}
|
||||
return curIndex;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package net.i2p.data.i2np;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
@@ -36,7 +35,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage {
|
||||
RECORD_COUNT = r;
|
||||
if (dataSize != calculateWrittenLength())
|
||||
throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")");
|
||||
_records = new ByteArray[RECORD_COUNT];
|
||||
_records = new EncryptedBuildRecord[RECORD_COUNT];
|
||||
super.readMessage(data, offset + 1, dataSize, type);
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage {
|
||||
// can't call super, written length check will fail
|
||||
//return super.writeMessageBody(out, curIndex + 1);
|
||||
for (int i = 0; i < RECORD_COUNT; i++) {
|
||||
System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE);
|
||||
System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE);
|
||||
curIndex += RECORD_SIZE;
|
||||
}
|
||||
return curIndex;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package net.i2p.data.i2np;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
@@ -38,7 +37,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
|
||||
RECORD_COUNT = r;
|
||||
if (dataSize != calculateWrittenLength())
|
||||
throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")");
|
||||
_records = new ByteArray[RECORD_COUNT];
|
||||
_records = new EncryptedBuildRecord[RECORD_COUNT];
|
||||
super.readMessage(data, offset + 1, dataSize, type);
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
|
||||
// can't call super, written length check will fail
|
||||
//return super.writeMessageBody(out, curIndex + 1);
|
||||
for (int i = 0; i < RECORD_COUNT; i++) {
|
||||
System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE);
|
||||
System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE);
|
||||
curIndex += RECORD_SIZE;
|
||||
}
|
||||
return curIndex;
|
||||
|
@@ -261,6 +261,18 @@ public class RouterInfo extends DatabaseEntry {
|
||||
return _options.getProperty(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* For convenience, the same as getOption("router.version"),
|
||||
* but returns "0" if unset.
|
||||
*
|
||||
* @return non-null, "0" if unknown.
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public String getVersion() {
|
||||
String rv = _options.getProperty("router.version");
|
||||
return rv != null ? rv : "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a set of options or statistics that the router can expose.
|
||||
* Makes a copy.
|
||||
|
@@ -513,9 +513,7 @@ class StoreJob extends JobImpl {
|
||||
* @since 0.7.10
|
||||
*/
|
||||
private static boolean supportsEncryption(RouterInfo ri) {
|
||||
String v = ri.getOption("router.version");
|
||||
if (v == null)
|
||||
return false;
|
||||
String v = ri.getVersion();
|
||||
return VersionComparator.comp(v, MIN_ENCRYPTION_VERSION) >= 0;
|
||||
}
|
||||
|
||||
@@ -535,9 +533,7 @@ class StoreJob extends JobImpl {
|
||||
}
|
||||
if (type == null)
|
||||
return false;
|
||||
String v = ri.getOption("router.version");
|
||||
if (v == null)
|
||||
return false;
|
||||
String v = ri.getVersion();
|
||||
String since = type.getSupportedSince();
|
||||
return VersionComparator.comp(v, since) >= 0;
|
||||
}
|
||||
@@ -549,9 +545,7 @@ class StoreJob extends JobImpl {
|
||||
* @since 0.9.12
|
||||
*/
|
||||
public static boolean supportsBigLeaseSets(RouterInfo ri) {
|
||||
String v = ri.getOption("router.version");
|
||||
if (v == null)
|
||||
return false;
|
||||
String v = ri.getVersion();
|
||||
return VersionComparator.comp(v, MIN_BIGLEASESET_VERSION) >= 0;
|
||||
}
|
||||
|
||||
|
@@ -367,6 +367,29 @@ public class ProfileOrganizer {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces integer subTierMode argument, for clarity
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public enum Slice {
|
||||
|
||||
SLICE_ALL(0x00, 0),
|
||||
SLICE_0_1(0x02, 0),
|
||||
SLICE_2_3(0x02, 2),
|
||||
SLICE_0(0x03, 0),
|
||||
SLICE_1(0x03, 1),
|
||||
SLICE_2(0x03, 2),
|
||||
SLICE_3(0x03, 3);
|
||||
|
||||
final int mask, val;
|
||||
|
||||
Slice(int mask, int val) {
|
||||
this.mask = mask;
|
||||
this.val = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a set of Hashes for peers that are both fast and reliable. If an insufficient
|
||||
* number of peers are both fast and reliable, fall back onto high capacity peers, and if that
|
||||
@@ -388,15 +411,15 @@ public class ProfileOrganizer {
|
||||
* 7: return only from group 3
|
||||
*</pre>
|
||||
*/
|
||||
public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, Hash randomKey, int subTierMode) {
|
||||
public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, Hash randomKey, Slice subTierMode) {
|
||||
getReadLock();
|
||||
try {
|
||||
if (subTierMode > 0) {
|
||||
if (subTierMode != Slice.SLICE_ALL) {
|
||||
int sz = _fastPeers.size();
|
||||
if (sz < 6 || (subTierMode >= 4 && sz < 12))
|
||||
subTierMode = 0;
|
||||
if (sz < 6 || (subTierMode.mask >= 3 && sz < 12))
|
||||
subTierMode = Slice.SLICE_ALL;
|
||||
}
|
||||
if (subTierMode > 0)
|
||||
if (subTierMode != Slice.SLICE_ALL)
|
||||
locked_selectPeers(_fastPeers, howMany, exclude, matches, randomKey, subTierMode);
|
||||
else
|
||||
locked_selectPeers(_fastPeers, howMany, exclude, matches, 2);
|
||||
@@ -674,9 +697,9 @@ public class ProfileOrganizer {
|
||||
// they probably don't have a TCP hole punched in their firewall either.
|
||||
RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
|
||||
if (info != null) {
|
||||
String v = info.getOption("router.version");
|
||||
String v = info.getVersion();
|
||||
// this only works if there is no 0.6.1.34!
|
||||
if (v != null && (!v.equals("0.6.1.33")) &&
|
||||
if ((!v.equals("0.6.1.33")) &&
|
||||
v.startsWith("0.6.1.") && info.getTargetAddress("NTCP") == null)
|
||||
l.add(peer);
|
||||
else {
|
||||
@@ -1302,7 +1325,8 @@ public class ProfileOrganizer {
|
||||
* 7: return only from group 3
|
||||
*</pre>
|
||||
*/
|
||||
private void locked_selectPeers(Map<Hash, PeerProfile> peers, int howMany, Set<Hash> toExclude, Set<Hash> matches, Hash randomKey, int subTierMode) {
|
||||
private void locked_selectPeers(Map<Hash, PeerProfile> peers, int howMany, Set<Hash> toExclude,
|
||||
Set<Hash> matches, Hash randomKey, Slice subTierMode) {
|
||||
List<Hash> all = new ArrayList<Hash>(peers.keySet());
|
||||
// use RandomIterator to avoid shuffling the whole thing
|
||||
for (Iterator<Hash> iter = new RandomIterator<Hash>(all); (matches.size() < howMany) && iter.hasNext(); ) {
|
||||
@@ -1314,13 +1338,8 @@ public class ProfileOrganizer {
|
||||
if (_us.equals(peer))
|
||||
continue;
|
||||
int subTier = getSubTier(peer, randomKey);
|
||||
if (subTierMode >= 4) {
|
||||
if (subTier != (subTierMode & 0x03))
|
||||
continue;
|
||||
} else {
|
||||
if ((subTier >> 1) != (subTierMode & 0x01))
|
||||
continue;
|
||||
}
|
||||
if ((subTier & subTierMode.mask) != subTierMode.val)
|
||||
continue;
|
||||
boolean ok = isSelectable(peer);
|
||||
if (ok)
|
||||
matches.add(peer);
|
||||
|
@@ -173,11 +173,22 @@ public abstract class TransportUtil {
|
||||
/**
|
||||
* Is this a valid port for us or a remote router?
|
||||
*
|
||||
* ref: http://i2p-projekt.i2p/en/docs/ports
|
||||
*
|
||||
* @since 0.9.17 moved from logic in individual transports
|
||||
*/
|
||||
public static boolean isValidPort(int port) {
|
||||
// update log message in UDPEndpoint if you update this list
|
||||
return port >= 1024 &&
|
||||
port <= 65535 &&
|
||||
port != 1900; // UPnP SSDP
|
||||
port != 1900 && // UPnP SSDP
|
||||
port != 2827 && // BOB
|
||||
port != 4444 && // HTTP
|
||||
port != 4445 && // HTTPS
|
||||
port != 6668 && // IRC
|
||||
(!(port >= 7650 && port <= 7664)) && // standard I2P range
|
||||
port != 8998 && // mtn
|
||||
port != 31000 && // Wrapper
|
||||
port != 32000; // Wrapper
|
||||
}
|
||||
}
|
||||
|
@@ -375,8 +375,8 @@ public class NTCPTransport extends TransportImpl {
|
||||
if (us != null) {
|
||||
RouterIdentity id = us.getIdentity();
|
||||
if (id.getSigType() != SigType.DSA_SHA1) {
|
||||
String v = toAddress.getOption("router.version");
|
||||
if (v != null && VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
|
||||
String v = toAddress.getVersion();
|
||||
if (VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
|
||||
markUnreachable(peer);
|
||||
return null;
|
||||
}
|
||||
|
@@ -113,8 +113,12 @@ class UDPEndpoint implements SocketListener {
|
||||
private DatagramSocket getSocket() {
|
||||
DatagramSocket socket = null;
|
||||
int port = _listenPort;
|
||||
if (port > 0 && !TransportUtil.isValidPort(port))
|
||||
_log.error("Specified UDP port is " + port + ", ports lower than 1024 not recommended");
|
||||
if (port > 0 && !TransportUtil.isValidPort(port)) {
|
||||
_log.error("Specified UDP port " + port + " is not valid, selecting a new port");
|
||||
// See isValidPort() for list
|
||||
_log.error("Invalid ports are: 0-1023, 1900, 2827, 4444, 4445, 6668, 7650-7664, 8998, 31000, 32000, 65536+");
|
||||
port = -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_PORT_RETRIES; i++) {
|
||||
if (port <= 0) {
|
||||
|
@@ -1232,7 +1232,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_context.simpleScheduler().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
|
||||
}
|
||||
markUnreachable(peerHash);
|
||||
_context.banlist().banlistRouter(peerHash, "Part of the wrong network, version = " + ((RouterInfo) entry).getOption("router.version"));
|
||||
_context.banlist().banlistRouter(peerHash, "Part of the wrong network, version = " + ((RouterInfo) entry).getVersion());
|
||||
//_context.banlist().banlistRouter(peerHash, "Part of the wrong network", STYLE);
|
||||
dropPeer(peerHash, false, "wrong network");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -1578,8 +1578,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
if (us != null) {
|
||||
RouterIdentity id = us.getIdentity();
|
||||
if (id.getSigType() != SigType.DSA_SHA1) {
|
||||
String v = toAddress.getOption("router.version");
|
||||
if (v != null && VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
|
||||
String v = toAddress.getVersion();
|
||||
if (VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
|
||||
markUnreachable(to);
|
||||
return null;
|
||||
}
|
||||
|
@@ -3,11 +3,11 @@ package net.i2p.router.tunnel;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.BuildRequestRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildMessage;
|
||||
|
||||
@@ -54,33 +54,41 @@ public abstract class BuildMessageGenerator {
|
||||
* containing the hop's configuration (as well as the reply info, if it is an outbound endpoint)
|
||||
*
|
||||
* @param msg out parameter
|
||||
* @throws IllegalArgumentException if hop bigger than config
|
||||
*/
|
||||
public static void createRecord(int recordNum, int hop, TunnelBuildMessage msg,
|
||||
TunnelCreatorConfig cfg, Hash replyRouter,
|
||||
long replyTunnel, I2PAppContext ctx, PublicKey peerKey) {
|
||||
byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE];
|
||||
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
|
||||
EncryptedBuildRecord erec;
|
||||
if (peerKey != null) {
|
||||
BuildRequestRecord req = null;
|
||||
if ( (!cfg.isInbound()) && (hop + 1 == cfg.getLength()) ) //outbound endpoint
|
||||
req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel);
|
||||
else
|
||||
req = createUnencryptedRecord(ctx, cfg, hop, null, -1);
|
||||
if (req == null)
|
||||
throw new IllegalArgumentException("hop bigger than config");
|
||||
Hash peer = cfg.getPeer(hop);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Record " + recordNum + "/" + hop + "/" + peer.toBase64()
|
||||
// + ": unencrypted = " + Base64.encode(req.getData().getData()));
|
||||
req.encryptRecord(ctx, peerKey, peer, encrypted, 0);
|
||||
erec = req.encryptRecord(ctx, peerKey, peer);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Record " + recordNum + "/" + hop + ": encrypted = " + Base64.encode(encrypted));
|
||||
} else {
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Record " + recordNum + "/" + hop + "/ is blank/random");
|
||||
byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE];
|
||||
ctx.random().nextBytes(encrypted);
|
||||
erec = new EncryptedBuildRecord(encrypted);
|
||||
}
|
||||
msg.setRecord(recordNum, new ByteArray(encrypted));
|
||||
msg.setRecord(recordNum, erec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if hop >= cfg.length
|
||||
*/
|
||||
private static BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop,
|
||||
Hash replyRouter, long replyTunnel) {
|
||||
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
|
||||
@@ -111,11 +119,11 @@ public abstract class BuildMessageGenerator {
|
||||
SessionKey layerKey = hopConfig.getLayerKey();
|
||||
SessionKey ivKey = hopConfig.getIVKey();
|
||||
SessionKey replyKey = hopConfig.getReplyKey();
|
||||
byte iv[] = hopConfig.getReplyIV().getData();
|
||||
if ( (iv == null) || (iv.length != BuildRequestRecord.IV_SIZE) ) {
|
||||
byte iv[] = hopConfig.getReplyIV();
|
||||
if (iv == null) {
|
||||
iv = new byte[BuildRequestRecord.IV_SIZE];
|
||||
ctx.random().nextBytes(iv);
|
||||
hopConfig.getReplyIV().setData(iv);
|
||||
hopConfig.setReplyIV(iv);
|
||||
}
|
||||
boolean isInGW = (cfg.isInbound() && (hop == 0));
|
||||
boolean isOutEnd = (!cfg.isInbound() && (hop + 1 >= cfg.getLength()));
|
||||
@@ -132,9 +140,9 @@ public abstract class BuildMessageGenerator {
|
||||
// log.debug("Hop " + hop + " has the next message ID of " + nextMsgId + " for " + cfg
|
||||
// + " with replyKey " + replyKey.toBase64() + " and replyIV " + Base64.encode(iv));
|
||||
|
||||
BuildRequestRecord rec= new BuildRequestRecord();
|
||||
rec.createRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer, nextMsgId, layerKey, ivKey, replyKey,
|
||||
iv, isInGW, isOutEnd);
|
||||
BuildRequestRecord rec= new BuildRequestRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer,
|
||||
nextMsgId, layerKey, ivKey, replyKey,
|
||||
iv, isInGW, isOutEnd);
|
||||
|
||||
return rec;
|
||||
} else {
|
||||
@@ -143,7 +151,11 @@ public abstract class BuildMessageGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the records so their hop ident is visible at the appropriate times
|
||||
* Encrypt the records so their hop ident is visible at the appropriate times.
|
||||
*
|
||||
* Note that this layer-encrypts the build records for the message in-place.
|
||||
* Only call this onece for a given message.
|
||||
*
|
||||
* @param order list of hop #s as Integers. For instance, if (order.get(1) is 4), it is peer cfg.getPeer(4)
|
||||
*/
|
||||
public static void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg,
|
||||
@@ -151,7 +163,7 @@ public abstract class BuildMessageGenerator {
|
||||
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
|
||||
// encrypt the records so that the right elements will be visible at the right time
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
ByteArray rec = msg.getRecord(i);
|
||||
EncryptedBuildRecord rec = msg.getRecord(i);
|
||||
Integer hopNum = order.get(i);
|
||||
int hop = hopNum.intValue();
|
||||
if ( (isBlank(cfg, hop)) || (!cfg.isInbound() && hop == 1) ) {
|
||||
@@ -166,12 +178,12 @@ public abstract class BuildMessageGenerator {
|
||||
for (int j = hop-1; j >= stop; j--) {
|
||||
HopConfig hopConfig = cfg.getConfig(j);
|
||||
SessionKey key = hopConfig.getReplyKey();
|
||||
byte iv[] = hopConfig.getReplyIV().getData();
|
||||
int off = rec.getOffset();
|
||||
byte iv[] = hopConfig.getReplyIV();
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg
|
||||
// + " with " + key.toBase64() + "/" + Base64.encode(iv));
|
||||
ctx.aes().decrypt(rec.getData(), off, rec.getData(), off, key, iv, TunnelBuildMessage.RECORD_SIZE);
|
||||
// corrupts the SDS
|
||||
ctx.aes().decrypt(rec.getData(), 0, rec.getData(), 0, key, iv, TunnelBuildMessage.RECORD_SIZE);
|
||||
}
|
||||
}
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
|
@@ -2,12 +2,13 @@ package net.i2p.router.tunnel;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.BuildRequestRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.TunnelBuildMessage;
|
||||
import net.i2p.router.util.DecayingBloomFilter;
|
||||
import net.i2p.router.util.DecayingHashSet;
|
||||
@@ -32,7 +33,10 @@ public class BuildMessageProcessor {
|
||||
* message (so that the reply can be placed in that position after going through the decrypted
|
||||
* request record).
|
||||
*
|
||||
* @return the current hop's decrypted record
|
||||
* Note that this layer-decrypts the build records in-place.
|
||||
* Do not call this more than once for a given message.
|
||||
*
|
||||
* @return the current hop's decrypted record or null on failure
|
||||
*/
|
||||
public BuildRequestRecord decrypt(I2PAppContext ctx, TunnelBuildMessage msg, Hash ourHash, PrivateKey privKey) {
|
||||
Log log = ctx.logManager().getLog(getClass());
|
||||
@@ -44,35 +48,33 @@ public class BuildMessageProcessor {
|
||||
long totalDup = 0;
|
||||
long beforeLoop = System.currentTimeMillis();
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
ByteArray rec = msg.getRecord(i);
|
||||
int off = rec.getOffset();
|
||||
EncryptedBuildRecord rec = msg.getRecord(i);
|
||||
int len = BuildRequestRecord.PEER_SIZE;
|
||||
long beforeEq = System.currentTimeMillis();
|
||||
boolean eq = DataHelper.eq(ourHash.getData(), 0, rec.getData(), off, len);
|
||||
boolean eq = DataHelper.eq(ourHash.getData(), 0, rec.getData(), 0, len);
|
||||
totalEq += System.currentTimeMillis()-beforeEq;
|
||||
if (eq) {
|
||||
long beforeIsDup = System.currentTimeMillis();
|
||||
boolean isDup = _filter.add(rec.getData(), off + len, 32);
|
||||
boolean isDup = _filter.add(rec.getData(), len, 32);
|
||||
totalDup += System.currentTimeMillis()-beforeIsDup;
|
||||
if (isDup) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.debug(msg.getUniqueId() + ": A record matching our hash was found, but it seems to be a duplicate");
|
||||
ctx.statManager().addRateData("tunnel.buildRequestDup", 1, 0);
|
||||
ctx.statManager().addRateData("tunnel.buildRequestDup", 1);
|
||||
return null;
|
||||
}
|
||||
BuildRequestRecord req = new BuildRequestRecord();
|
||||
beforeActualDecrypt = System.currentTimeMillis();
|
||||
boolean ok = req.decryptRecord(ctx, privKey, ourHash, rec);
|
||||
afterActualDecrypt = System.currentTimeMillis();
|
||||
if (ok) {
|
||||
try {
|
||||
BuildRequestRecord req = new BuildRequestRecord(ctx, privKey, rec);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(msg.getUniqueId() + ": A record matching our hash was found and decrypted");
|
||||
rv = req;
|
||||
} else {
|
||||
} catch (DataFormatException dfe) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(msg.getUniqueId() + ": A record matching our hash was found, but could not be decrypted");
|
||||
return null; // our hop is invalid? b0rkage
|
||||
}
|
||||
afterActualDecrypt = System.currentTimeMillis();
|
||||
ourHop = i;
|
||||
}
|
||||
}
|
||||
@@ -89,11 +91,12 @@ public class BuildMessageProcessor {
|
||||
int ivOff = 0;
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
if (i != ourHop) {
|
||||
ByteArray data = msg.getRecord(i);
|
||||
EncryptedBuildRecord data = msg.getRecord(i);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Encrypting record " + i + "/?/" + data.getOffset() + "/" + data.getValid() + " with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv, ivOff, 16));
|
||||
ctx.aes().encrypt(data.getData(), data.getOffset(), data.getData(), data.getOffset(), replyKey,
|
||||
iv, ivOff, data.getValid());
|
||||
log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv, ivOff, 16));
|
||||
// corrupts SDS
|
||||
ctx.aes().encrypt(data.getData(), 0, data.getData(), 0, replyKey,
|
||||
iv, ivOff, data.length());
|
||||
}
|
||||
}
|
||||
long afterEncrypt = System.currentTimeMillis();
|
||||
|
@@ -4,10 +4,10 @@ import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleByteCache;
|
||||
@@ -34,6 +34,9 @@ public class BuildReplyHandler {
|
||||
* Decrypt the tunnel build reply records. This overwrites the contents of the reply.
|
||||
* Thread safe (no state).
|
||||
*
|
||||
* Note that this layer-decrypts the build records in-place.
|
||||
* Do not call this more than once for a given message.
|
||||
*
|
||||
* @return status for the records (in record order), or null if the replies were not valid. Fake records
|
||||
* always have 0 as their value
|
||||
*/
|
||||
@@ -70,7 +73,10 @@ public class BuildReplyHandler {
|
||||
/**
|
||||
* Decrypt the record (removing the layers of reply encyption) and read out the status
|
||||
*
|
||||
* @return -1 on decrypt failure
|
||||
* Note that this layer-decrypts the build records in-place.
|
||||
* Do not call this more than once for a given message.
|
||||
*
|
||||
* @return the status 0-255, or -1 on decrypt failure
|
||||
*/
|
||||
private int decryptRecord(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) {
|
||||
if (BuildMessageGenerator.isBlank(cfg, hop)) {
|
||||
@@ -78,9 +84,8 @@ public class BuildReplyHandler {
|
||||
log.debug(reply.getUniqueId() + ": Record " + recordNum + "/" + hop + " is fake, so consider it valid...");
|
||||
return 0;
|
||||
}
|
||||
ByteArray rec = reply.getRecord(recordNum);
|
||||
EncryptedBuildRecord rec = reply.getRecord(recordNum);
|
||||
byte[] data = rec.getData();
|
||||
int off = rec.getOffset();
|
||||
int start = cfg.getLength() - 1;
|
||||
if (cfg.isInbound())
|
||||
start--; // the last hop in an inbound tunnel response doesn't actually encrypt
|
||||
@@ -88,35 +93,34 @@ public class BuildReplyHandler {
|
||||
for (int j = start; j >= hop; j--) {
|
||||
HopConfig hopConfig = cfg.getConfig(j);
|
||||
SessionKey replyKey = hopConfig.getReplyKey();
|
||||
byte replyIV[] = hopConfig.getReplyIV().getData();
|
||||
int replyIVOff = hopConfig.getReplyIV().getOffset();
|
||||
byte replyIV[] = hopConfig.getReplyIV();
|
||||
if (log.shouldLog(Log.DEBUG)) {
|
||||
log.debug(reply.getUniqueId() + ": Decrypting record " + recordNum + "/" + hop + "/" + j + " with replyKey "
|
||||
+ replyKey.toBase64() + "/" + Base64.encode(replyIV, replyIVOff, 16) + ": " + cfg);
|
||||
log.debug(reply.getUniqueId() + ": before decrypt("+ off + "-"+(off+rec.getValid())+"): " + Base64.encode(data, off, rec.getValid()));
|
||||
log.debug(reply.getUniqueId() + ": Full reply rec: offset=" + off + ", sz=" + data.length + "/" + rec.getValid() + ", data=" + Base64.encode(data, off, TunnelBuildReplyMessage.RECORD_SIZE));
|
||||
+ replyKey.toBase64() + "/" + Base64.encode(replyIV) + ": " + cfg);
|
||||
log.debug(reply.getUniqueId() + ": before decrypt: " + Base64.encode(data));
|
||||
log.debug(reply.getUniqueId() + ": Full reply rec: sz=" + data.length + " data=" + Base64.encode(data, 0, TunnelBuildReplyMessage.RECORD_SIZE));
|
||||
}
|
||||
ctx.aes().decrypt(data, off, data, off, replyKey, replyIV, replyIVOff, TunnelBuildReplyMessage.RECORD_SIZE);
|
||||
ctx.aes().decrypt(data, 0, data, 0, replyKey, replyIV, 0, TunnelBuildReplyMessage.RECORD_SIZE);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(reply.getUniqueId() + ": after decrypt: " + Base64.encode(data, off, rec.getValid()));
|
||||
log.debug(reply.getUniqueId() + ": after decrypt: " + Base64.encode(data));
|
||||
}
|
||||
// ok, all of the layered encryption is stripped, so lets verify it
|
||||
// (formatted per BuildResponseRecord.create)
|
||||
// don't cache the result
|
||||
//Hash h = ctx.sha().calculateHash(data, off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH);
|
||||
byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH);
|
||||
ctx.sha().calculateHash(data, off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH, h, 0);
|
||||
boolean ok = DataHelper.eq(h, 0, data, off, Hash.HASH_LENGTH);
|
||||
ctx.sha().calculateHash(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH, h, 0);
|
||||
boolean ok = DataHelper.eq(h, 0, data, 0, Hash.HASH_LENGTH);
|
||||
if (!ok) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(reply.getUniqueId() + ": Failed verification on " + recordNum + "/" + hop + ": " + Base64.encode(h) + " calculated, " +
|
||||
Base64.encode(data, off, Hash.HASH_LENGTH) + " expected\n" +
|
||||
"Record: " + Base64.encode(data, off+Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH));
|
||||
Base64.encode(data, 0, Hash.HASH_LENGTH) + " expected\n" +
|
||||
"Record: " + Base64.encode(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH));
|
||||
SimpleByteCache.release(h);
|
||||
return -1;
|
||||
} else {
|
||||
SimpleByteCache.release(h);
|
||||
int rv = (int)DataHelper.fromLong(data, off + TunnelBuildReplyMessage.RECORD_SIZE - 1, 1);
|
||||
int rv = (int)DataHelper.fromLong(data, TunnelBuildReplyMessage.RECORD_SIZE - 1, 1);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(reply.getUniqueId() + ": Verified: " + rv + " for record " + recordNum + "/" + hop);
|
||||
return rv;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package net.i2p.router.tunnel;
|
||||
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
@@ -20,7 +19,7 @@ public class HopConfig {
|
||||
private SessionKey _layerKey;
|
||||
private SessionKey _ivKey;
|
||||
private SessionKey _replyKey;
|
||||
private ByteArray _replyIV;
|
||||
private byte[] _replyIV;
|
||||
private long _creation;
|
||||
private long _expiration;
|
||||
//private Map _options;
|
||||
@@ -87,9 +86,23 @@ public class HopConfig {
|
||||
public SessionKey getReplyKey() { return _replyKey; }
|
||||
public void setReplyKey(SessionKey key) { _replyKey = key; }
|
||||
|
||||
/** iv used to encrypt the reply sent for the new tunnel creation crypto */
|
||||
public ByteArray getReplyIV() { return _replyIV; }
|
||||
public void setReplyIV(ByteArray iv) { _replyIV = iv; }
|
||||
/**
|
||||
* IV used to encrypt the reply sent for the new tunnel creation crypto
|
||||
*
|
||||
* @return 16 bytes
|
||||
*/
|
||||
public byte[] getReplyIV() { return _replyIV; }
|
||||
|
||||
/**
|
||||
* IV used to encrypt the reply sent for the new tunnel creation crypto
|
||||
*
|
||||
* @throws IllegalArgumentException if not 16 bytes
|
||||
*/
|
||||
public void setReplyIV(byte[] iv) {
|
||||
if (iv.length != REPLY_IV_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
_replyIV = iv;
|
||||
}
|
||||
|
||||
/** when does this tunnel expire (in ms since the epoch)? */
|
||||
public long getExpiration() { return _expiration; }
|
||||
|
@@ -6,7 +6,6 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
@@ -14,6 +13,7 @@ import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.BuildRequestRecord;
|
||||
import net.i2p.data.i2np.BuildResponseRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
||||
@@ -782,13 +782,13 @@ class BuildHandler implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
byte reply[] = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
|
||||
EncryptedBuildRecord reply = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
|
||||
int records = state.msg.getRecordCount();
|
||||
int ourSlot = -1;
|
||||
for (int j = 0; j < records; j++) {
|
||||
if (state.msg.getRecord(j) == null) {
|
||||
ourSlot = j;
|
||||
state.msg.setRecord(j, new ByteArray(reply));
|
||||
state.msg.setRecord(j, reply);
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Full reply record for slot " + ourSlot + "/" + ourId + "/" + nextId + "/" + req.readReplyMessageId()
|
||||
// + ": " + Base64.encode(reply));
|
||||
|
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PublicKey;
|
||||
@@ -90,7 +89,7 @@ abstract class BuildRequestor {
|
||||
cfg.getConfig(i-1).setSendTunnelId(cfg.getConfig(i).getReceiveTunnelId());
|
||||
byte iv[] = new byte[16];
|
||||
ctx.random().nextBytes(iv);
|
||||
cfg.getConfig(i).setReplyIV(new ByteArray(iv));
|
||||
cfg.getConfig(i).setReplyIV(iv);
|
||||
cfg.getConfig(i).setReplyKey(ctx.keyGenerator().generateSessionKey());
|
||||
}
|
||||
// This is in BuildExecutor.buildTunnel() now
|
||||
@@ -239,9 +238,7 @@ abstract class BuildRequestor {
|
||||
RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(h);
|
||||
if (ri == null)
|
||||
return false;
|
||||
String v = ri.getOption("router.version");
|
||||
if (v == null)
|
||||
return false;
|
||||
String v = ri.getVersion();
|
||||
return VersionComparator.comp(v, MIN_VARIABLE_VERSION) >= 0;
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import java.util.Set;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import static net.i2p.router.peermanager.ProfileOrganizer.Slice.*;
|
||||
|
||||
/**
|
||||
* Pick peers randomly out of the fast pool, and put them into tunnels
|
||||
@@ -65,7 +66,7 @@ class ClientPeerSelector extends TunnelPeerSelector {
|
||||
} else {
|
||||
firstHopExclude = exclude;
|
||||
}
|
||||
ctx.profileOrganizer().selectFastPeers(1, firstHopExclude, matches, settings.getRandomKey(), length == 2 ? 2 : 4);
|
||||
ctx.profileOrganizer().selectFastPeers(1, firstHopExclude, matches, settings.getRandomKey(), length == 2 ? SLICE_0_1 : SLICE_0);
|
||||
matches.remove(ctx.routerHash());
|
||||
exclude.addAll(matches);
|
||||
rv.addAll(matches);
|
||||
@@ -73,7 +74,7 @@ class ClientPeerSelector extends TunnelPeerSelector {
|
||||
if (length > 2) {
|
||||
// middle hop(s)
|
||||
// group 2 or 3
|
||||
ctx.profileOrganizer().selectFastPeers(length - 2, exclude, matches, settings.getRandomKey(), 3);
|
||||
ctx.profileOrganizer().selectFastPeers(length - 2, exclude, matches, settings.getRandomKey(), SLICE_2_3);
|
||||
matches.remove(ctx.routerHash());
|
||||
if (matches.size() > 1) {
|
||||
// order the middle peers for tunnels >= 4 hops
|
||||
@@ -96,7 +97,7 @@ class ClientPeerSelector extends TunnelPeerSelector {
|
||||
if (moreExclude != null)
|
||||
exclude.addAll(moreExclude);
|
||||
}
|
||||
ctx.profileOrganizer().selectFastPeers(1, exclude, matches, settings.getRandomKey(), length == 2 ? 3 : 5);
|
||||
ctx.profileOrganizer().selectFastPeers(1, exclude, matches, settings.getRandomKey(), length == 2 ? SLICE_2_3 : SLICE_1);
|
||||
matches.remove(ctx.routerHash());
|
||||
rv.addAll(matches);
|
||||
}
|
||||
|
@@ -349,9 +349,7 @@ public abstract class TunnelPeerSelector {
|
||||
if (known != null) {
|
||||
for (int i = 0; i < known.size(); i++) {
|
||||
RouterInfo peer = known.get(i);
|
||||
String v = peer.getOption("router.version");
|
||||
if (v == null)
|
||||
continue;
|
||||
String v = peer.getVersion();
|
||||
// RI sigtypes added in 0.9.16
|
||||
// SSU inbound connection bug fixed in 0.9.17, but it won't bid, so NTCP only,
|
||||
// no need to check
|
||||
@@ -402,8 +400,8 @@ public abstract class TunnelPeerSelector {
|
||||
// so don't exclude it based on published capacity
|
||||
|
||||
// minimum version check
|
||||
String v = peer.getOption("router.version");
|
||||
if (v == null || VersionComparator.comp(v, MIN_VERSION) < 0)
|
||||
String v = peer.getVersion();
|
||||
if (VersionComparator.comp(v, MIN_VERSION) < 0)
|
||||
return true;
|
||||
|
||||
// uptime is always spoofed to 90m, so just remove all this
|
||||
|
Reference in New Issue
Block a user