propagate from branch 'i2p.i2p.zzz.test2' (head 0feb2e6806927f68c7333aaa0892de185bb2629c)

to branch 'i2p.i2p' (head 0482fa843cb1e9d7ec281440056eef3a0ab07bdb)
This commit is contained in:
zzz
2014-12-05 15:14:40 +00:00
55 changed files with 666 additions and 295 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 . -->
<!-- -->
<!-- ========================================================================= -->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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