diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index ba1c5d74d..92fc90e27 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -37,6 +37,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; +import net.i2p.router.transport.crypto.X25519KeyFactory; import net.i2p.router.transport.ntcp.NTCPTransport; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; @@ -68,6 +69,7 @@ public class TransportManager implements TransportEventListener { private final RouterContext _context; private final UPnPManager _upnpManager; private final DHSessionKeyBuilder.PrecalcRunner _dhThread; + private final X25519KeyFactory _xdhThread; /** default true */ public final static String PROP_ENABLE_UDP = "i2np.udp.enable"; @@ -76,6 +78,9 @@ public class TransportManager implements TransportEventListener { /** default true */ public final static String PROP_ENABLE_UPNP = "i2np.upnp.enable"; + private static final String PROP_NTCP2_ENABLE = "i2np.ntcp2.enable"; + private static final boolean DEFAULT_NTCP2_ENABLE = false; + private static final String PROP_ADVANCED = "routerconsole.advanced"; /** not forever, since they may update */ @@ -98,6 +103,9 @@ public class TransportManager implements TransportEventListener { else _upnpManager = null; _dhThread = new DHSessionKeyBuilder.PrecalcRunner(context); + boolean enableNTCP2 = isNTCPEnabled(context) && + context.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE); + _xdhThread = enableNTCP2 ? new X25519KeyFactory(context) : null; } /** @@ -172,7 +180,7 @@ public class TransportManager implements TransportEventListener { initializeAddress(udp); } if (isNTCPEnabled(_context)) { - Transport ntcp = new NTCPTransport(_context, _dhThread); + Transport ntcp = new NTCPTransport(_context, _dhThread, _xdhThread); addTransport(ntcp); initializeAddress(ntcp); if (udp != null) { @@ -309,6 +317,8 @@ public class TransportManager implements TransportEventListener { synchronized void startListening() { if (_dhThread.getState() == Thread.State.NEW) _dhThread.start(); + if (_xdhThread != null && _xdhThread.getState() == Thread.State.NEW) + _xdhThread.start(); // For now, only start UPnP if we have no publicly-routable addresses // so we don't open the listener ports to the world. // Maybe we need a config option to force on? Probably not. @@ -719,6 +729,7 @@ public class TransportManager implements TransportEventListener { _context.banlist().banlistRouterForever(peer, _x("Unsupported signature type")); } else if (unreachableTransports >= _transports.size() && countActivePeers() > 0) { // Don't banlist if we aren't talking to anybody, as we may have a network connection issue + // TODO if we are IPv6 only, ban for longer boolean incompat = false; RouterInfo us = _context.router().getRouterInfo(); if (us != null) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java index a9bf59aa5..115ecca60 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java @@ -9,6 +9,11 @@ import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; /** + * Inbound NTCP 1 or 2. Outbound NTCP 1 only. + * OutboundNTCP2State does not extend this. + * + * NTCP 1 establishement overview: + * * Handle the 4-phase establishment, which is as follows: * *
@@ -33,7 +38,7 @@ import net.i2p.util.SimpleByteCache;
* X, Y: 256 byte DH keys
* H(): 32 byte SHA256 Hash
* E(data, session key, IV): AES256 Encrypt
- * S(): 40 byte DSA Signature
+ * S(): 40 byte DSA Signature, or length as implied by sig type
* tsA, tsB: timestamps (4 bytes, seconds since epoch)
* sk: 32 byte Session key
* sz: 2 byte size of Alice identity to follow
@@ -85,17 +90,11 @@ abstract class EstablishBase implements EstablishState {
/** bytes received so far */
protected int _received;
- private byte _extra[];
protected final DHSessionKeyBuilder _dh;
protected final NTCPTransport _transport;
protected final NTCPConnection _con;
- /** error causing the corruption */
- private String _err;
- /** exception causing the error */
- private Exception _e;
- private boolean _failedBySkew;
protected static final int MIN_RI_SIZE = 387;
protected static final int MAX_RI_SIZE = 3072;
@@ -133,6 +132,42 @@ abstract class EstablishBase implements EstablishState {
/** got 1, sent 2, got 3 */
IB_GOT_RI,
+ /**
+ * Next state IB_NTCP2_GOT_X
+ * @since 0.9.36
+ */
+ IB_NTCP2_INIT,
+ /**
+ * Got Noise part of msg 1
+ * Next state IB_NTCP2_GOT_PADDING or IB_NTCP2_READ_RANDOM on fail
+ * @since 0.9.36
+ */
+ IB_NTCP2_GOT_X,
+ /**
+ * Got msg 1 incl. padding
+ * Next state IB_NTCP2_SENT_Y
+ * @since 0.9.36
+ */
+ IB_NTCP2_GOT_PADDING,
+ /**
+ * Sent msg 2 and padding
+ * Next state IB_NTCP2_GOT_RI
+ * @since 0.9.36
+ */
+ IB_NTCP2_SENT_Y,
+ /**
+ * Got msg 3
+ * Next state VERIFIED
+ * @since 0.9.36
+ */
+ IB_NTCP2_GOT_RI,
+ /**
+ * Got msg 1 and failed AEAD
+ * Next state CORRUPT
+ * @since 0.9.36
+ */
+ IB_NTCP2_READ_RANDOM,
+
/** OB: got and verified 4; IB: got and verified 3 and sent 4 */
VERIFIED,
CORRUPT
@@ -178,13 +213,13 @@ abstract class EstablishBase implements EstablishState {
}
/**
- * parse the contents of the buffer as part of the handshake. if the
- * handshake is completed and there is more data remaining, the data are
- * copieed out so that the next read will be the (still encrypted) remaining
- * data (available from getExtraBytes)
+ * Parse the contents of the buffer as part of the handshake.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
+ *
+ * If there are additional data in the buffer after the handshake is complete,
+ * the EstablishState is responsible for passing it to NTCPConnection.
*/
public synchronized void receive(ByteBuffer src) {
synchronized(_stateLock) {
@@ -202,11 +237,6 @@ abstract class EstablishBase implements EstablishState {
*/
public void prepareOutbound() {}
- /**
- * Was this connection failed because of clock skew?
- */
- public synchronized boolean getFailedBySkew() { return _failedBySkew; }
-
/** did the handshake fail for some reason? */
public boolean isCorrupt() {
synchronized(_stateLock) {
@@ -234,31 +264,6 @@ abstract class EstablishBase implements EstablishState {
*/
public abstract int getVersion();
- /** Anything left over in the byte buffer after verification is extra
- *
- * All data must be copied out of the buffer as Reader.processRead()
- * will return it to the pool.
- *
- * State must be VERIFIED.
- * Caller must synch.
- */
- protected void prepareExtra(ByteBuffer buf) {
- int remaining = buf.remaining();
- if (remaining > 0) {
- _extra = new byte[remaining];
- buf.get(_extra);
- _received += remaining;
- }
- if (_log.shouldLog(Log.DEBUG))
- _log.debug(prefix() + "prepare extra " + remaining + " (total received: " + _received + ")");
- }
-
- /**
- * if complete, this will contain any bytes received as part of the
- * handshake that were after the actual handshake. This may return null.
- */
- public synchronized byte[] getExtraBytes() { return _extra; }
-
/**
* Release resources on timeout.
* @param e may be null
@@ -281,12 +286,12 @@ abstract class EstablishBase implements EstablishState {
return;
changeState(State.CORRUPT);
}
- _failedBySkew = bySkew;
- _err = reason;
- _e = e;
if (_log.shouldLog(Log.WARN))
- _log.warn(prefix()+"Failed to establish: " + _err, e);
+ _log.warn(prefix() + "Failed to establish: " + reason, e);
+ if (!bySkew)
+ _context.statManager().addRateData("ntcp.receiveCorruptEstablishment", 1);
releaseBufs(false);
+ // con.close()?
}
/**
@@ -303,10 +308,6 @@ abstract class EstablishBase implements EstablishState {
_transport.returnUnused(_dh);
}
- public synchronized String getError() { return _err; }
-
- public synchronized Exception getException() { return _e; }
-
/**
* XOR a into b. Modifies b. a is unmodified.
* @param a 32 bytes
@@ -328,7 +329,7 @@ abstract class EstablishBase implements EstablishState {
buf.append("IBES ");
else
buf.append("OBES ");
- buf.append(System.identityHashCode(this));
+ buf.append(_con.toString());
buf.append(' ').append(_state);
if (_con.isEstablished()) buf.append(" established");
buf.append(": ");
@@ -347,10 +348,20 @@ abstract class EstablishBase implements EstablishState {
public int getVersion() { return 1; }
+ /*
+ * @throws IllegalStateException always
+ */
+ @Override
+ public void receive(ByteBuffer src) {
+ throw new IllegalStateException("receive() " + src.remaining() + " on verified state, doing nothing!");
+ }
+
+ /*
+ * @throws IllegalStateException always
+ */
@Override
public void prepareOutbound() {
- Log log = RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class);
- log.warn("prepareOutbound() on verified state, doing nothing!");
+ throw new IllegalStateException("prepareOutbound() on verified state, doing nothing!");
}
@Override
@@ -369,10 +380,20 @@ abstract class EstablishBase implements EstablishState {
public int getVersion() { return 1; }
+ /*
+ * @throws IllegalStateException always
+ */
+ @Override
+ public void receive(ByteBuffer src) {
+ throw new IllegalStateException("receive() " + src.remaining() + " on failed state, doing nothing!");
+ }
+
+ /*
+ * @throws IllegalStateException always
+ */
@Override
public void prepareOutbound() {
- Log log = RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class);
- log.warn("prepareOutbound() on verified state, doing nothing!");
+ throw new IllegalStateException("prepareOutbound() on failed state, doing nothing!");
}
@Override
diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
index 24695b07d..4991a2b87 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
@@ -9,13 +9,15 @@ import java.nio.ByteBuffer;
interface EstablishState {
/**
- * parse the contents of the buffer as part of the handshake. if the
- * handshake is completed and there is more data remaining, the data are
- * copieed out so that the next read will be the (still encrypted) remaining
- * data (available from getExtraBytes)
+ * Parse the contents of the buffer as part of the handshake.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
+ *
+ * If there are additional data in the buffer after the handshake is complete,
+ * the EstablishState is responsible for passing it to NTCPConnection.
+ *
+ * @throws IllegalStateException
*/
public void receive(ByteBuffer src);
@@ -23,14 +25,11 @@ interface EstablishState {
* Does nothing. Outbound (Alice) must override.
* We are establishing an outbound connection, so prepare ourselves by
* queueing up the write of the first part of the handshake
+ *
+ * @throws IllegalStateException
*/
public void prepareOutbound();
- /**
- * Was this connection failed because of clock skew?
- */
- public boolean getFailedBySkew();
-
/** did the handshake fail for some reason? */
public boolean isCorrupt();
@@ -43,12 +42,6 @@ interface EstablishState {
*/
public boolean isComplete();
- /**
- * if complete, this will contain any bytes received as part of the
- * handshake that were after the actual handshake. This may return null.
- */
- public byte[] getExtraBytes();
-
/**
* Get the NTCP version
* @return 1, 2, or 0 if unknown
@@ -63,7 +56,4 @@ interface EstablishState {
*/
public void close(String reason, Exception e);
- public String getError();
-
- public Exception getException();
}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
index 5fa4e4ca2..0a637405a 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
@@ -283,6 +283,8 @@ class EventPumper implements Runnable {
con.getTimeSinceReceive() > expire) {
// we haven't sent or received anything in a really long time, so lets just close 'er up
con.close();
+ if (_log.shouldInfo())
+ _log.info("Failsafe or expire close for " + con);
failsafeCloses++;
}
} catch (CancelledKeyException cke) {
@@ -300,6 +302,7 @@ class EventPumper implements Runnable {
}
} else {
// another 100% CPU workaround
+ // TODO remove or only if we appear to be looping with no interest ops
if ((loopCount % 512) == 511) {
if (_log.shouldLog(Log.INFO))
_log.info("EventPumper throttle " + loopCount + " loops in " +
@@ -549,7 +552,9 @@ class EventPumper implements Runnable {
chan.socket().setKeepAlive(true);
SelectionKey ckey = chan.register(_selector, SelectionKey.OP_READ);
- new NTCPConnection(_context, _transport, chan, ckey);
+ NTCPConnection con = new NTCPConnection(_context, _transport, chan, ckey);
+ ckey.attach(con);
+ _transport.establishing(con);
} catch (IOException ioe) {
_log.error("Error accepting", ioe);
}
@@ -565,6 +570,7 @@ class EventPumper implements Runnable {
if (connected) {
if (shouldSetKeepAlive(chan))
chan.socket().setKeepAlive(true);
+ // key was already set when the channel was created, why do it again here?
con.setKey(key);
con.outboundConnected();
_context.statManager().addRateData("ntcp.connectSuccessful", 1);
@@ -619,7 +625,7 @@ class EventPumper implements Runnable {
ByteArray ba = new ByteArray(ip);
count = _blockedIPs.increment(ba);
if (_log.shouldLog(Log.WARN))
- _log.warn("Blocking IP " + Addresses.toString(ip) + " with count " + count + ": " + con);
+ _log.warn("EOF on inbound before receiving any, blocking IP " + Addresses.toString(ip) + " with count " + count + ": " + con);
} else {
count = 1;
if (_log.shouldLog(Log.WARN))
@@ -682,11 +688,11 @@ class EventPumper implements Runnable {
ByteArray ba = new ByteArray(ip);
count = _blockedIPs.increment(ba);
if (_log.shouldLog(Log.WARN))
- _log.warn("Blocking IP " + Addresses.toString(ip) + " with count " + count + ": " + con);
+ _log.warn("Blocking IP " + Addresses.toString(ip) + " with count " + count + ": " + con, ioe);
} else {
count = 1;
if (_log.shouldLog(Log.WARN))
- _log.warn("IOE on inbound before receiving any: " + con);
+ _log.warn("IOE on inbound before receiving any: " + con, ioe);
}
_context.statManager().addRateData("ntcp.dropInboundNoMessage", count);
} else {
diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
index 11088e05c..dd8e3d14f 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
@@ -5,17 +5,35 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import com.southernstorm.noise.protocol.CipherState;
+import com.southernstorm.noise.protocol.CipherStatePair;
+import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.crypto.SigType;
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.router.RouterIdentity;
+import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
+import net.i2p.data.i2np.I2NPMessage;
+import net.i2p.data.i2np.I2NPMessageException;
+import net.i2p.data.router.RouterAddress;
+import net.i2p.data.router.RouterIdentity;
+import net.i2p.data.router.RouterInfo;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
+import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
+import static net.i2p.router.transport.ntcp.OutboundNTCP2State.*;
+import net.i2p.util.ByteCache;
import net.i2p.util.Log;
import net.i2p.util.SimpleByteCache;
@@ -25,7 +43,7 @@ import net.i2p.util.SimpleByteCache;
*
* @since 0.9.35 pulled out of EstablishState
*/
-class InboundEstablishState extends EstablishBase {
+class InboundEstablishState extends EstablishBase implements NTCP2Payload.PayloadCallback {
/** current encrypted block we are reading (IB only) or an IV buf used at the end for OB */
private byte _curEncrypted[];
@@ -39,7 +57,36 @@ class InboundEstablishState extends EstablishBase {
/** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */
private int _sz_aliceIdent_tsA_padding_aliceSigSize;
+ //// NTCP2 things
+
+ private HandshakeState _handshakeState;
+ private int _padlen1;
+ private int _msg3p2len;
+ private int _msg3p2FailReason = -1;
+ private ByteArray _msg3tmp;
+ private NTCP2Options _hisPadding;
+
+ // same as I2PTunnelRunner
+ private static final int BUFFER_SIZE = 4*1024;
+ private static final int MAX_DATA_READ_BUFS = 32;
+ private static final ByteCache _dataReadBufs = ByteCache.getInstance(MAX_DATA_READ_BUFS, BUFFER_SIZE);
+
private static final int NTCP1_MSG1_SIZE = XY_SIZE + HXY_SIZE;
+ // 287 - 64 = 223
+ private static final int PADDING1_MAX = TOTAL1_MAX - MSG1_SIZE;
+ private static final int PADDING1_FAIL_MAX = 128;
+ private static final int PADDING2_MAX = 64;
+ // DSA RI, no options, no addresses
+ private static final int RI_MIN = 387 + 8 + 1 + 1 + 2 + 40;
+ private static final int MSG3P2_MIN = 1 + 2 + 1 + RI_MIN + MAC_SIZE;
+ // absolute max, let's enforce less
+ //private static final int MSG3P2_MAX = BUFFER_SIZE - MSG3P1_SIZE;
+ private static final int MSG3P2_MAX = 6000;
+
+ private static final Set STATES_NTCP2 =
+ EnumSet.of(State.IB_NTCP2_INIT, State.IB_NTCP2_GOT_X, State.IB_NTCP2_GOT_PADDING,
+ State.IB_NTCP2_SENT_Y, State.IB_NTCP2_GOT_RI, State.IB_NTCP2_READ_RANDOM);
+
public InboundEstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
super(ctx, transport, con);
@@ -50,13 +97,13 @@ class InboundEstablishState extends EstablishBase {
}
/**
- * parse the contents of the buffer as part of the handshake. if the
- * handshake is completed and there is more data remaining, the data are
- * copieed out so that the next read will be the (still encrypted) remaining
- * data (available from getExtraBytes)
+ * Parse the contents of the buffer as part of the handshake.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
+ *
+ * If there are additional data in the buffer after the handshake is complete,
+ * the EstablishState is responsible for passing it to NTCPConnection.
*/
@Override
public synchronized void receive(ByteBuffer src) {
@@ -77,7 +124,8 @@ class InboundEstablishState extends EstablishBase {
synchronized (_stateLock) {
if (_state == State.IB_INIT)
return 0;
- // TODO NTCP2 states
+ if (STATES_NTCP2.contains(_state))
+ return 2;
return 1;
}
}
@@ -91,15 +139,24 @@ class InboundEstablishState extends EstablishBase {
*
* Caller must synch.
*
- * FIXME none of the _state comparisons use _stateLock, but whole thing
- * is synchronized, should be OK. See isComplete()
*/
private void receiveInbound(ByteBuffer src) {
+ if (STATES_NTCP2.contains(_state)) {
+ receiveInboundNTCP2(src);
+ return;
+ }
+ // TODO if less than 64, buffer and decide later?
if (_state == State.IB_INIT && src.hasRemaining()) {
int remaining = src.remaining();
- //if (remaining < NTCP1_MSG1_SIZE && _transport.isNTCP2Enabled()) {
- // // NTCP2
- //}
+ if (remaining < NTCP1_MSG1_SIZE && _transport.isNTCP2Enabled()) {
+ // NTCP2
+ // TODO can't change our mind later if we get more than 287
+ _con.setVersion(2);
+ changeState(State.IB_NTCP2_INIT);
+ receiveInboundNTCP2(src);
+ // releaseBufs() will return the unused DH
+ return;
+ }
int toGet = Math.min(remaining, XY_SIZE - _received);
src.get(_X, _received, toGet);
_received += toGet;
@@ -188,6 +245,10 @@ class InboundEstablishState extends EstablishBase {
_context.statManager().addRateData("ntcp.invalidDH", 1);
fail("Invalid X", e);
return;
+ } catch (IllegalStateException ise) {
+ // setPeerPublicValue()
+ fail("reused keys?", ise);
+ return;
}
}
@@ -281,9 +342,7 @@ class InboundEstablishState extends EstablishBase {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "got the sig");
- verifyInbound();
- if (_state == State.VERIFIED && src.hasRemaining())
- prepareExtra(src);
+ verifyInbound(src);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"verifying size (sz=" + _sz_aliceIdent_tsA_padding_aliceSig.size()
+ " expected=" + _sz_aliceIdent_tsA_padding_aliceSigSize
@@ -291,10 +350,15 @@ class InboundEstablishState extends EstablishBase {
+ ')');
return;
}
- } else {
}
}
+ // check for remaining data
+ if ((_state == State.VERIFIED || _state == State.CORRUPT) && src.hasRemaining()) {
+ if (_log.shouldWarn())
+ _log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
+ }
+
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"done with the data, not yet complete or corrupt");
}
@@ -343,6 +407,7 @@ class InboundEstablishState extends EstablishBase {
/**
* We are Bob. Verify message #3 from Alice, then send message #4 to Alice.
+ * NTCP 1 only.
*
* _aliceIdentSize and _aliceIdent must be set.
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least
@@ -358,9 +423,12 @@ class InboundEstablishState extends EstablishBase {
* transport
*
* State must be IB_GOT_RI.
+ * This will always change the state to VERIFIED or CORRUPT.
* Caller must synch.
+ *
+ * @param buf possibly containing "extra" data for data phase
*/
- private void verifyInbound() {
+ private void verifyInbound(ByteBuffer buf) {
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
try {
int sz = _aliceIdentSize;
@@ -393,64 +461,35 @@ class InboundEstablishState extends EstablishBase {
System.arraycopy(b, b.length-s.length, s, 0, s.length);
Signature sig = new Signature(type, s);
boolean ok = _context.dsa().verifySignature(sig, toVerify, _aliceIdent.getSigningPublicKey());
+ Hash aliceHash = _aliceIdent.calculateHash();
+ if (ok) {
+ ok = verifyInbound(aliceHash);
+ }
if (ok) {
- // get inet-addr
- InetAddress addr = this._con.getChannel().socket().getInetAddress();
- byte[] ip = (addr == null) ? null : addr.getAddress();
- if (_context.banlist().isBanlistedForever(_aliceIdent.calculateHash())) {
- if (_log.shouldLog(Log.WARN))
- _log.warn("Dropping inbound connection from permanently banlisted peer: " + _aliceIdent.calculateHash());
- // So next time we will not accept the con from this IP,
- // rather than doing the whole handshake
- if(ip != null)
- _context.blocklist().add(ip);
- fail("Peer is banlisted forever: " + _aliceIdent.calculateHash());
- return;
- }
- if(ip != null)
- _transport.setIP(_aliceIdent.calculateHash(), ip);
- if (_log.shouldLog(Log.DEBUG))
- _log.debug(prefix() + "verification successful for " + _con);
-
- long diff = 1000*Math.abs(_peerSkew);
- if (!_context.clock().getUpdatedSuccessfully()) {
- // Adjust the clock one time in desperation
- // This isn't very likely, outbound will do it first
- // We are Bob, she is Alice, adjust to match Alice
- _context.clock().setOffset(1000 * (0 - _peerSkew), true);
- _peerSkew = 0;
- if (diff != 0)
- _log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
- } else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
- _context.statManager().addRateData("ntcp.invalidInboundSkew", diff);
- _transport.markReachable(_aliceIdent.calculateHash(), true);
- // Only banlist if we know what time it is
- _context.banlist().banlistRouter(DataHelper.formatDuration(diff),
- _aliceIdent.calculateHash(),
- _x("Excessive clock skew: {0}"));
- _transport.setLastBadSkew(_peerSkew);
- fail("Clocks too skewed (" + diff + " ms)", null, true);
- return;
- } else if (_log.shouldLog(Log.DEBUG)) {
- _log.debug(prefix()+"Clock skew: " + diff + " ms");
- }
-
_con.setRemotePeer(_aliceIdent);
- sendInboundConfirm(_aliceIdent, tsA);
+ sendInboundConfirm(aliceHash, tsA);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
byte iv[] = _curEncrypted; // reuse buf
System.arraycopy(_e_bobSig, _e_bobSig.length-AES_SIZE, iv, 0, AES_SIZE);
// this does not copy the IV, do not release to cache
// We are Bob, she is Alice, clock skew is Alice-Bob
- _con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted); // skew in seconds
+ // skew in seconds
+ _con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted);
+ changeState(State.VERIFIED);
+ if (buf.hasRemaining()) {
+ // process "extra" data
+ // This is unlikely for inbound, as we must reply with message 4
+ if (_log.shouldWarn())
+ _log.warn("extra data " + buf.remaining() + " on " + this);
+ _con.recvEncryptedI2NP(buf);
+ }
releaseBufs(true);
if (_log.shouldLog(Log.INFO))
- _log.info(prefix()+"Verified remote peer as " + _aliceIdent.calculateHash());
- changeState(State.VERIFIED);
+ _log.info(prefix()+"Verified remote peer as " + aliceHash);
} else {
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
- fail("Peer verification failed - spoof of " + _aliceIdent.calculateHash() + "?");
+ // verifyInbound(aliceHash) called fail()
}
} catch (IOException ioe) {
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
@@ -458,19 +497,76 @@ class InboundEstablishState extends EstablishBase {
}
}
+ /**
+ * Common validation things for both NTCP 1 and 2.
+ * Call after receiving Alice's RouterIdentity (in message 3).
+ * _peerSkew must be set.
+ *
+ * Side effect: sets _msg3p2FailReason when returning false
+ *
+ * @return success or calls fail() and returns false
+ * @since 0.9.36 pulled out of verifyInbound()
+ */
+ private boolean verifyInbound(Hash aliceHash) {
+ // get inet-addr
+ InetAddress addr = this._con.getChannel().socket().getInetAddress();
+ byte[] ip = (addr == null) ? null : addr.getAddress();
+ if (_context.banlist().isBanlistedForever(aliceHash)) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Dropping inbound connection from permanently banlisted peer: " + aliceHash);
+ // So next time we will not accept the con from this IP,
+ // rather than doing the whole handshake
+ if(ip != null)
+ _context.blocklist().add(ip);
+ fail("Peer is banlisted forever: " + aliceHash);
+ _msg3p2FailReason = NTCPConnection.REASON_BANNED;
+ return false;
+ }
+ if(ip != null)
+ _transport.setIP(aliceHash, ip);
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug(prefix() + "verification successful for " + _con);
+
+ long diff = 1000*Math.abs(_peerSkew);
+ if (!_context.clock().getUpdatedSuccessfully()) {
+ // Adjust the clock one time in desperation
+ // This isn't very likely, outbound will do it first
+ // We are Bob, she is Alice, adjust to match Alice
+ _context.clock().setOffset(1000 * (0 - _peerSkew), true);
+ _peerSkew = 0;
+ if (diff != 0)
+ _log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
+ } else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
+ _context.statManager().addRateData("ntcp.invalidInboundSkew", diff);
+ _transport.markReachable(aliceHash, true);
+ // Only banlist if we know what time it is
+ _context.banlist().banlistRouter(DataHelper.formatDuration(diff),
+ aliceHash,
+ _x("Excessive clock skew: {0}"));
+ _transport.setLastBadSkew(_peerSkew);
+ fail("Clocks too skewed (" + diff + " ms)", null, true);
+ _msg3p2FailReason = NTCPConnection.REASON_SKEW;
+ return false;
+ } else if (_log.shouldLog(Log.DEBUG)) {
+ _log.debug(prefix()+"Clock skew: " + diff + " ms");
+ }
+ return true;
+ }
+
/**
* We are Bob. Send message #4 to Alice.
*
* State must be VERIFIED.
* Caller must synch.
+ *
+ * @param h Alice's Hash
*/
- private void sendInboundConfirm(RouterIdentity alice, long tsA) {
+ private void sendInboundConfirm(Hash h, long tsA) {
// send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev)
byte toSign[] = new byte[XY_SIZE + XY_SIZE + 32+4+4];
int off = 0;
System.arraycopy(_X, 0, toSign, off, XY_SIZE); off += XY_SIZE;
System.arraycopy(_Y, 0, toSign, off, XY_SIZE); off += XY_SIZE;
- Hash h = alice.calculateHash();
System.arraycopy(h.getData(), 0, toSign, off, 32); off += 32;
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
@@ -496,6 +592,438 @@ class InboundEstablishState extends EstablishBase {
_transport.getPumper().wantsWrite(_con, _e_bobSig);
}
+ //// NTCP2 below here
+
+ /**
+ * NTCP2 only. State must be one of IB_NTCP2_*
+ *
+ * we are Bob, so receive these bytes as part of an inbound connection
+ * This method receives messages 1 and 3, and sends message 2.
+ *
+ * All data must be copied out of the buffer as Reader.processRead()
+ * will return it to the pool.
+ *
+ * @since 0.9.36
+ */
+ private synchronized void receiveInboundNTCP2(ByteBuffer src) {
+ if (_state == State.IB_NTCP2_INIT && src.hasRemaining()) {
+ // use _X for the buffer
+ int toGet = Math.min(src.remaining(), MSG1_SIZE - _received);
+ src.get(_X, _received, toGet);
+ _received += toGet;
+ if (_received < MSG1_SIZE) {
+ // TODO if we got less than 64 should we even be here?
+ if (_log.shouldWarn())
+ _log.warn("Short buffer got " + toGet + " total now " + _received);
+ return;
+ }
+ changeState(State.IB_NTCP2_GOT_X);
+ _received = 0;
+
+ // replay check using encrypted key
+ if (!_transport.isHXHIValid(_X)) {
+ _context.statManager().addRateData("ntcp.replayHXxorBIH", 1);
+ fail("Replay msg 1, eX = " + Base64.encode(_X, 0, KEY_SIZE));
+ return;
+ }
+
+ try {
+ _handshakeState = new HandshakeState(HandshakeState.RESPONDER, _transport.getXDHFactory());
+ } catch (GeneralSecurityException gse) {
+ throw new IllegalStateException("bad proto", gse);
+ }
+ _handshakeState.getLocalKeyPair().setPublicKey(_transport.getNTCP2StaticPubkey(), 0);
+ _handshakeState.getLocalKeyPair().setPrivateKey(_transport.getNTCP2StaticPrivkey(), 0);
+ Hash h = _context.routerHash();
+ SessionKey bobHash = new SessionKey(h.getData());
+ // save encrypted data for CBC for msg 2
+ System.arraycopy(_X, KEY_SIZE - IV_SIZE, _prevEncrypted, 0, IV_SIZE);
+ _context.aes().decrypt(_X, 0, _X, 0, bobHash, _transport.getNTCP2StaticIV(), KEY_SIZE);
+ if (DataHelper.eqCT(_X, 0, ZEROKEY, 0, KEY_SIZE)) {
+ fail("Bad msg 1, X = 0");
+ return;
+ }
+ byte options[] = new byte[OPTIONS1_SIZE];
+ try {
+ _handshakeState.start();
+ if (_log.shouldWarn())
+ _log.warn("After start: " + _handshakeState.toString());
+ _handshakeState.readMessage(_X, 0, MSG1_SIZE, options, 0);
+ } catch (GeneralSecurityException gse) {
+ // Read a random number of bytes, store wanted in _padlen1
+ _padlen1 = _context.random().nextInt(PADDING1_FAIL_MAX) - src.remaining();
+ if (_padlen1 > 0) {
+ // delayed fail for probing resistance
+ // need more bytes before failure
+ if (_log.shouldWarn())
+ _log.warn("Bad msg 1, X = " + Base64.encode(_X, 0, KEY_SIZE) + " with " + src.remaining() +
+ " more bytes, waiting for " + _padlen1 + " more bytes", gse);
+ changeState(State.IB_NTCP2_READ_RANDOM);
+ } else {
+ // got all we need, fail now
+ fail("Bad msg 1, X = " + Base64.encode(_X, 0, KEY_SIZE) + " remaining = " + src.remaining(), gse);
+ }
+ return;
+ } catch (RuntimeException re) {
+ fail("Bad msg 1, X = " + Base64.encode(_X, 0, KEY_SIZE), re);
+ return;
+ }
+ if (_log.shouldWarn())
+ _log.warn("After msg 1: " + _handshakeState.toString());
+ int v = options[1] & 0xff;
+ if (v != NTCPTransport.NTCP2_INT_VERSION) {
+ fail("Bad version: " + v);
+ return;
+ }
+ _padlen1 = (int) DataHelper.fromLong(options, 2, 2);
+ _msg3p2len = (int) DataHelper.fromLong(options, 4, 2);
+ long tsA = DataHelper.fromLong(options, 8, 4);
+ long now = _context.clock().now();
+ // In NTCP1, timestamp comes in msg 3 so we know the RTT.
+ // In NTCP2, it comes in msg 1, so just guess.
+ // We could defer this to msg 3 to calculate the RTT?
+ long rtt = 250;
+ _peerSkew = (now - (tsA * 1000) - (rtt / 2) + 500) / 1000;
+ if ((_peerSkew > MAX_SKEW || _peerSkew < 0 - MAX_SKEW) &&
+ !_context.clock().getUpdatedSuccessfully()) {
+ // If not updated successfully, allow it.
+ // This isn't very likely, outbound will do it first
+ // See verifyInbound() above.
+ fail("Clock Skew: " + _peerSkew, null, true);
+ return;
+ }
+ if (_padlen1 > PADDING1_MAX) {
+ fail("bad msg 1 padlen: " + _padlen1);
+ return;
+ }
+ if (_msg3p2len < MSG3P2_MIN || _msg3p2len > MSG3P2_MAX) {
+ fail("bad msg3p2 len: " + _msg3p2len);
+ return;
+ }
+ if (_padlen1 <= 0) {
+ // No padding specified, go straight to sending msg 2
+ changeState(State.IB_NTCP2_GOT_PADDING);
+ if (src.hasRemaining()) {
+ // Inbound conn can never have extra data after msg 1
+ fail("Extra data after msg 1: " + src.remaining());
+ } else {
+ // write msg 2
+ prepareOutbound2();
+ }
+ return;
+ }
+ }
+
+ // delayed fail for probing resistance
+ if (_state == State.IB_NTCP2_READ_RANDOM && src.hasRemaining()) {
+ // read more bytes before failing
+ _received += src.remaining();
+ if (_received < _padlen1) {
+ if (_log.shouldWarn())
+ _log.warn("Bad msg 1, got " + src.remaining() +
+ " more bytes, waiting for " + (_padlen1 - _received) + " more bytes");
+ } else {
+ fail("Bad msg 1, failing after getting " + src.remaining() + " more bytes");
+ }
+ return;
+ }
+
+ if (_state == State.IB_NTCP2_GOT_X && src.hasRemaining()) {
+ // skip this if _padlen1 == 0;
+ // use _X for the buffer
+ int toGet = Math.min(src.remaining(), _padlen1 - _received);
+ src.get(_X, _received, toGet);
+ _received += toGet;
+ if (_received < _padlen1)
+ return;
+ changeState(State.IB_NTCP2_GOT_PADDING);
+ _handshakeState.mixHash(_X, 0, _padlen1);
+ if (_log.shouldWarn())
+ _log.warn("After mixhash padding " + _padlen1 + " msg 1: " + _handshakeState.toString());
+ _received = 0;
+ if (src.hasRemaining()) {
+ // Inbound conn can never have extra data after msg 1
+ fail("Extra data after msg 1: " + src.remaining());
+ } else {
+ // write msg 2
+ prepareOutbound2();
+ }
+ return;
+ }
+
+ if (_state == State.IB_NTCP2_SENT_Y && src.hasRemaining()) {
+ int msg3tot = MSG3P1_SIZE + _msg3p2len;
+ if (_msg3tmp == null)
+ _msg3tmp = _dataReadBufs.acquire();
+ // use _X for the buffer FIXME too small
+ byte[] tmp = _msg3tmp.getData();
+ int toGet = Math.min(src.remaining(), msg3tot - _received);
+ src.get(tmp, _received, toGet);
+ _received += toGet;
+ if (_received < msg3tot)
+ return;
+ changeState(State.IB_NTCP2_GOT_RI);
+ _received = 0;
+ ByteArray ptmp = _dataReadBufs.acquire();
+ byte[] payload = ptmp.getData();
+ try {
+ _handshakeState.readMessage(tmp, 0, msg3tot, payload, 0);
+ } catch (GeneralSecurityException gse) {
+ // TODO delayed failure per spec, as in NTCPConnection.delayedClose()
+ _dataReadBufs.release(ptmp, false);
+ fail("Bad msg 3, part 1 is:\n" + net.i2p.util.HexDump.dump(tmp, 0, MSG3P1_SIZE), gse);
+ return;
+ } catch (RuntimeException re) {
+ _dataReadBufs.release(ptmp, false);
+ fail("Bad msg 3", re);
+ return;
+ }
+ if (_log.shouldWarn())
+ _log.warn("After msg 3: " + _handshakeState.toString());
+ try {
+ // calls callbacks below
+ NTCP2Payload.processPayload(_context, this, payload, 0, _msg3p2len - MAC_SIZE, true);
+ } catch (IOException ioe) {
+ fail("Bad msg 3 payload", ioe);
+ // probably payload frame/block problems
+ // setDataPhase() will send termination
+ if (_msg3p2FailReason < 0)
+ _msg3p2FailReason = NTCPConnection.REASON_FRAMING;
+ } catch (DataFormatException dfe) {
+ fail("Bad msg 3 payload", dfe);
+ // probably RI problems
+ // setDataPhase() will send termination
+ if (_msg3p2FailReason < 0)
+ _msg3p2FailReason = NTCPConnection.REASON_SIGFAIL;
+ _context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
+ } catch (I2NPMessageException ime) {
+ // shouldn't happen, no I2NP msgs in msg3p2
+ fail("Bad msg 3 payload", ime);
+ // setDataPhase() will send termination
+ if (_msg3p2FailReason < 0)
+ _msg3p2FailReason = 0;
+ } finally {
+ _dataReadBufs.release(ptmp, false);
+ }
+
+ // pass buffer for processing of "extra" data
+ setDataPhase(src);
+ }
+ // TODO check for remaining data and log/throw
+ }
+
+ /**
+ * Write the 2nd NTCP2 message.
+ * IV (CBC from msg 1) must be in _prevEncrypted
+ *
+ * @since 0.9.36
+ */
+ private synchronized void prepareOutbound2() {
+ // create msg 2 payload
+ byte[] options2 = new byte[OPTIONS2_SIZE];
+ int padlen2 = _context.random().nextInt(PADDING2_MAX);
+ DataHelper.toLong(options2, 2, 2, padlen2);
+ long now = _context.clock().now() / 1000;
+ DataHelper.toLong(options2, 8, 4, now);
+ byte[] tmp = new byte[MSG2_SIZE + padlen2];
+ try {
+ _handshakeState.writeMessage(tmp, 0, options2, 0, OPTIONS2_SIZE);
+ } catch (GeneralSecurityException gse) {
+ // buffer length error
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 2 out", gse);
+ fail("Bad msg 2 out", gse);
+ return;
+ } catch (RuntimeException re) {
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 2 out", re);
+ fail("Bad msg 2 out", re);
+ return;
+ }
+ if (_log.shouldWarn())
+ _log.warn("After msg 2: " + _handshakeState.toString());
+ Hash h = _context.routerHash();
+ SessionKey bobHash = new SessionKey(h.getData());
+ _context.aes().encrypt(tmp, 0, tmp, 0, bobHash, _prevEncrypted, KEY_SIZE);
+ if (padlen2 > 0) {
+ _context.random().nextBytes(tmp, MSG2_SIZE, padlen2);
+ _handshakeState.mixHash(tmp, MSG2_SIZE, padlen2);
+ if (_log.shouldWarn())
+ _log.warn("After mixhash padding " + padlen2 + " msg 2: " + _handshakeState.toString());
+ }
+
+ changeState(State.IB_NTCP2_SENT_Y);
+ // send it all at once
+ _transport.getPumper().wantsWrite(_con, tmp);
+ }
+
+ /**
+ * KDF for NTCP2 data phase,
+ * then calls con.finishInboundEstablishment(),
+ * passing over the final keys and states to the con.
+ *
+ * This changes the state to VERIFIED.
+ *
+ * @param buf possibly containing "extra" data for data phase
+ * @since 0.9.36
+ */
+ private synchronized void setDataPhase(ByteBuffer buf) {
+ // Data phase ChaChaPoly keys
+ CipherStatePair ckp = _handshakeState.split();
+ CipherState rcvr = ckp.getReceiver();
+ CipherState sender = ckp.getSender();
+ byte[] k_ab = rcvr.getKey();
+ byte[] k_ba = sender.getKey();
+
+ // Data phase SipHash keys
+ byte[][] sipkeys = generateSipHashKeys(_context, _handshakeState);
+ byte[] sip_ab = sipkeys[0];
+ byte[] sip_ba = sipkeys[1];
+
+ if (_msg3p2FailReason >= 0) {
+ if (_log.shouldWarn())
+ _log.warn("Failed msg3p2, code " + _msg3p2FailReason + " for " + this);
+ _con.failInboundEstablishment(sender, sip_ba, _msg3p2FailReason);
+ } else {
+ if (_log.shouldWarn()) {
+ _log.warn("Finished establishment for " + this +
+ "\nGenerated ChaCha key for A->B: " + Base64.encode(k_ab) +
+ "\nGenerated ChaCha key for B->A: " + Base64.encode(k_ba) +
+ "\nGenerated SipHash key for A->B: " + Base64.encode(sip_ab) +
+ "\nGenerated SipHash key for B->A: " + Base64.encode(sip_ba));
+ }
+ // skew in seconds
+ _con.finishInboundEstablishment(sender, rcvr, sip_ba, sip_ab, _peerSkew, _hisPadding);
+ changeState(State.VERIFIED);
+ if (buf.hasRemaining()) {
+ // process "extra" data
+ // This is very likely for inbound, as data should come right after message 3
+ if (_log.shouldInfo())
+ _log.info("extra data " + buf.remaining() + " on " + this);
+ _con.recvEncryptedI2NP(buf);
+ }
+ }
+ // zero out everything
+ releaseBufs(true);
+ _handshakeState.destroy();
+ Arrays.fill(sip_ab, (byte) 0);
+ Arrays.fill(sip_ba, (byte) 0);
+ }
+
+ //// PayloadCallbacks
+
+ /**
+ * Get "s" static key out of RI, compare to what we got in the handshake.
+ * Tell NTCPConnection who it is.
+ *
+ * @param isHandshake always true
+ * @throws DataFormatException on bad sig, unknown SigType, no static key,
+ * static key mismatch, IP checks in verifyInbound()
+ * @since 0.9.36
+ */
+ public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
+ // Validate Alice static key
+ String s = null;
+ // find address with matching version
+ List addrs = ri.getTargetAddresses(NTCPTransport.STYLE, NTCPTransport.STYLE2);
+ for (RouterAddress addr : addrs) {
+ String v = addr.getOption("v");
+ if (v == null ||
+ (!v.equals(NTCPTransport.NTCP2_VERSION) && !v.startsWith(NTCPTransport.NTCP2_VERSION_ALT))) {
+ continue;
+ }
+ s = addr.getOption("s");
+ if (s != null)
+ break;
+ }
+ if (s == null) {
+ _msg3p2FailReason = NTCPConnection.REASON_S_MISMATCH;
+ throw new DataFormatException("no s in RI");
+ }
+ byte[] sb = Base64.decode(s);
+ if (sb == null || sb.length != KEY_SIZE) {
+ _msg3p2FailReason = NTCPConnection.REASON_S_MISMATCH;
+ throw new DataFormatException("bad s in RI");
+ }
+ byte[] nb = new byte[32];
+ // compare to the _handshakeState
+ _handshakeState.getRemotePublicKey().getPublicKey(nb, 0);
+ if (!DataHelper.eqCT(sb, 0, nb, 0, KEY_SIZE)) {
+ _msg3p2FailReason = NTCPConnection.REASON_S_MISMATCH;
+ throw new DataFormatException("s mismatch in RI");
+ }
+ _aliceIdent = ri.getIdentity();
+ Hash h = _aliceIdent.calculateHash();
+ // this sets the reason
+ boolean ok = verifyInbound(h);
+ if (!ok)
+ throw new DataFormatException("NTCP2 verifyInbound() fail");
+ try {
+ RouterInfo old = _context.netDb().store(h, ri);
+ if (flood && !ri.equals(old)) {
+ FloodfillNetworkDatabaseFacade fndf = (FloodfillNetworkDatabaseFacade) _context.netDb();
+ if (fndf.floodConditional(ri)) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Flooded the RI: " + h);
+ } else {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Flood request but we didn't: " + h);
+ }
+ }
+ } catch (IllegalArgumentException iae) {
+ // hash collision?
+ _msg3p2FailReason = NTCPConnection.REASON_UNSPEC;
+ throw new DataFormatException("RI store fail", iae);
+ }
+ _con.setRemotePeer(_aliceIdent);
+ }
+
+ /** @since 0.9.36 */
+ public void gotOptions(byte[] options, boolean isHandshake) {
+ if (options.length < 12) {
+ if (_log.shouldWarn())
+ _log.warn("Got options length " + options.length + " on: " + this);
+ return;
+ }
+ float tmin = (options[0] & 0xff) / 16.0f;
+ float tmax = (options[1] & 0xff) / 16.0f;
+ float rmin = (options[2] & 0xff) / 16.0f;
+ float rmax = (options[3] & 0xff) / 16.0f;
+ int tdummy = (int) DataHelper.fromLong(options, 4, 2);
+ int rdummy = (int) DataHelper.fromLong(options, 6, 2);
+ int tdelay = (int) DataHelper.fromLong(options, 8, 2);
+ int rdelay = (int) DataHelper.fromLong(options, 10, 2);
+ _hisPadding = new NTCP2Options(tmin, tmax, rmin, rmax,
+ tdummy, rdummy, tdelay, rdelay);
+ }
+
+ /** @since 0.9.36 */
+ public void gotPadding(int paddingLength, int frameLength) {}
+
+ // Following 4 are illegal in handshake, we will never get them
+
+ /** @since 0.9.36 */
+ public void gotTermination(int reason, long lastReceived) {}
+ /** @since 0.9.36 */
+ public void gotUnknown(int type, int len) {}
+ /** @since 0.9.36 */
+ public void gotDateTime(long time) {}
+ /** @since 0.9.36 */
+ public void gotI2NP(I2NPMessage msg) {}
+
+ /**
+ * @since 0.9.16
+ */
+ @Override
+ protected synchronized void fail(String reason, Exception e, boolean bySkew) {
+ super.fail(reason, e, bySkew);
+ if (_handshakeState != null) {
+ if (_log.shouldWarn())
+ _log.warn("State at failure: " + _handshakeState.toString());
+ _handshakeState.destroy();
+ }
+ }
+
/**
* Only call once. Caller must synch.
* @since 0.9.16
@@ -507,6 +1035,11 @@ class InboundEstablishState extends EstablishBase {
// NTCPConnection to use as the IV
if (!isVerified)
SimpleByteCache.release(_curEncrypted);
+ Arrays.fill(_X, (byte) 0);
SimpleByteCache.release(_X);
+ if (_msg3tmp != null) {
+ _dataReadBufs.release(_msg3tmp, false);
+ _msg3tmp = null;
+ }
}
}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
index 685a67a61..c659772ed 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
@@ -6,7 +6,10 @@ import java.net.Inet6Address;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.Set;
@@ -17,9 +20,14 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.Adler32;
+import com.southernstorm.noise.protocol.CipherState;
+
+import net.i2p.crypto.SipHashInline;
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.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
@@ -31,18 +39,22 @@ import net.i2p.data.i2np.I2NPMessageHandler;
import net.i2p.router.OutNetMessage;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
+import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.router.transport.FIFOBandwidthLimiter.Request;
+import net.i2p.router.transport.ntcp.NTCP2Payload.Block;
import net.i2p.router.util.PriBlockingQueue;
import net.i2p.util.ByteCache;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
+import net.i2p.util.SimpleTimer2;
import net.i2p.util.SystemVersion;
import net.i2p.util.VersionComparator;
/**
* Coordinate the connection to a single peer.
+ * NTCP 1 or 2.
*
* Public only for UI peers page. Not a public API, not for external use.
*
@@ -103,21 +115,13 @@ public class NTCPConnection implements Closeable {
//private final CoDelPriorityBlockingQueue _outbound;
private final PriBlockingQueue _outbound;
/**
- * current prepared OutNetMessage, or null - synchronize on _outbound to modify or read
- * FIXME why do we need this???
+ * current prepared OutNetMessages, or empty - synchronize to modify or read
*/
- private OutNetMessage _currentOutbound;
+ private final List _currentOutbound;
private SessionKey _sessionKey;
- /** encrypted block of the current I2NP message being read */
- private byte _curReadBlock[];
- /** next byte to which data should be placed in the _curReadBlock */
- private int _curReadBlockIndex;
- private final byte _decryptBlockBuf[];
- /** last AES block of the encrypted I2NP message (to serve as the next block's IV) */
- private byte _prevReadBlock[];
private byte _prevWriteEnd[];
/** current partially read I2NP message */
- private final ReadState _curReadState;
+ private ReadState _curReadState;
private final AtomicInteger _messagesRead = new AtomicInteger();
private final AtomicInteger _messagesWritten = new AtomicInteger();
private long _lastSendTime;
@@ -126,13 +130,11 @@ public class NTCPConnection implements Closeable {
private final long _created;
// prevent sending meta before established
private long _nextMetaTime = Long.MAX_VALUE;
- private int _consecutiveZeroReads;
+ private final AtomicInteger _consecutiveZeroReads = new AtomicInteger();
private static final int BLOCK_SIZE = 16;
private static final int META_SIZE = BLOCK_SIZE;
- /** unencrypted outbound metadata buffer */
- private final byte _meta[] = new byte[META_SIZE];
private boolean _sendingMeta;
/** how many consecutive sends were failed due to (estimated) send queue time */
//private int _consecutiveBacklog;
@@ -170,72 +172,102 @@ public class NTCPConnection implements Closeable {
private static final AtomicLong __connID = new AtomicLong();
private final long _connID = __connID.incrementAndGet();
+ //// NTCP2 things
+
+ private static final int PADDING_RAND_MIN = 16;
+ private static final int PADDING_MAX = 64;
+ private static final int SIP_IV_LENGTH = 8;
+ private static final int NTCP2_FAIL_READ = 1024;
+ private static final long NTCP2_FAIL_TIMEOUT = 10*1000;
+ private static final long NTCP2_TERMINATION_CLOSE_DELAY = 50;
+ static final int REASON_UNSPEC = 0;
+ static final int REASON_TERMINATION = 1;
+ static final int REASON_TIMEOUT = 2;
+ static final int REASON_AEAD = 4;
+ static final int REASON_OPTIONS = 5;
+ static final int REASON_SIGTYPE = 6;
+ static final int REASON_SKEW = 7;
+ static final int REASON_PADDING = 8;
+ static final int REASON_FRAMING = 9;
+ static final int REASON_PAYLOAD = 10;
+ static final int REASON_MSG1 = 11;
+ static final int REASON_MSG2 = 12;
+ static final int REASON_MSG3 = 13;
+ static final int REASON_FRAME_TIMEOUT = 14;
+ static final int REASON_SIGFAIL = 15;
+ static final int REASON_S_MISMATCH = 16;
+ static final int REASON_BANNED = 17;
+ static final int PADDING_MIN_DEFAULT_INT = 1;
+ static final int PADDING_MAX_DEFAULT_INT = 2;
+ private static final float PADDING_MIN_DEFAULT = PADDING_MIN_DEFAULT_INT / 16.0f;
+ private static final float PADDING_MAX_DEFAULT = PADDING_MAX_DEFAULT_INT / 16.0f;
+ static final int DUMMY_DEFAULT = 0;
+ static final int DELAY_DEFAULT = 0;
+ private static final NTCP2Options OUR_PADDING = new NTCP2Options(PADDING_MIN_DEFAULT, PADDING_MAX_DEFAULT,
+ PADDING_MIN_DEFAULT, PADDING_MAX_DEFAULT,
+ DUMMY_DEFAULT, DUMMY_DEFAULT,
+ DELAY_DEFAULT, DELAY_DEFAULT);
+ private static final int MIN_PADDING_RANGE = 64;
+ private NTCP2Options _paddingConfig;
+ private int _version;
+ private CipherState _sender;
+ private long _sendSipk1, _sendSipk2;
+ private byte[] _sendSipIV;
+
+
/**
- * Create an inbound connected (though not established) NTCP connection
- *
+ * Create an inbound connected (though not established) NTCP connection.
+ * Caller MUST call transport.establishing(this) after construction.
+ * Caller MUST key.attach(this) after construction.
*/
public NTCPConnection(RouterContext ctx, NTCPTransport transport, SocketChannel chan, SelectionKey key) {
- _context = ctx;
- _log = ctx.logManager().getLog(getClass());
- _created = ctx.clock().now();
- _transport = transport;
- _remAddr = null;
+ this(ctx, transport, null, true);
_chan = chan;
- _readBufs = new ConcurrentLinkedQueue();
- _writeBufs = new ConcurrentLinkedQueue();
- _bwInRequests = new ConcurrentHashSet(2);
- _bwOutRequests = new ConcurrentHashSet(8);
- //_outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32);
- _outbound = new PriBlockingQueue(ctx, "NTCP-Connection", 32);
- _isInbound = true;
- _decryptBlockBuf = new byte[BLOCK_SIZE];
- _curReadState = new ReadState();
- _establishState = new InboundEstablishState(ctx, transport, this);
+ _version = 1;
_conKey = key;
- _conKey.attach(this);
- _inboundListener = new InboundListener();
- _outboundListener = new OutboundListener();
- initialize();
+ _establishState = new InboundEstablishState(ctx, transport, this);
}
/**
- * Create an outbound unconnected NTCP connection
+ * Create an outbound unconnected NTCP connection.
+ * Caller MUST call transport.establishing(this) after construction.
*
* @param version must be 1 or 2
*/
public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer,
RouterAddress remAddr, int version) {
+ this(ctx, transport, remAddr, false);
+ _remotePeer = remotePeer;
+ _version = version;
+ if (version == 1)
+ _establishState = new OutboundEstablishState(ctx, transport, this);
+ else
+ _establishState = new OutboundNTCP2State(ctx, transport, this);
+ }
+
+ /**
+ * Base constructor in/out
+ * @since 0.9.36
+ */
+ private NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterAddress remAddr, boolean isIn) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
_created = ctx.clock().now();
_transport = transport;
- _remotePeer = remotePeer;
_remAddr = remAddr;
+ _lastSendTime = _created;
+ _lastReceiveTime = _created;
+ _lastRateUpdated = _created;
_readBufs = new ConcurrentLinkedQueue();
_writeBufs = new ConcurrentLinkedQueue();
_bwInRequests = new ConcurrentHashSet(2);
_bwOutRequests = new ConcurrentHashSet(8);
//_outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32);
_outbound = new PriBlockingQueue(ctx, "NTCP-Connection", 32);
- _isInbound = false;
- //if (version == 1)
- _establishState = new OutboundEstablishState(ctx, transport, this);
- //else
- // _establishState = // TODO
- _decryptBlockBuf = new byte[BLOCK_SIZE];
- _curReadState = new ReadState();
+ _currentOutbound = new ArrayList(1);
+ _isInbound = isIn;
_inboundListener = new InboundListener();
_outboundListener = new OutboundListener();
- initialize();
- }
-
- private void initialize() {
- _lastSendTime = _created;
- _lastReceiveTime = _created;
- _lastRateUpdated = _created;
- _curReadBlock = new byte[BLOCK_SIZE];
- _prevReadBlock = new byte[BLOCK_SIZE];
- _transport.establishing(this);
}
/**
@@ -249,6 +281,7 @@ public class NTCPConnection implements Closeable {
public SelectionKey getKey() { return _conKey; }
public void setChannel(SocketChannel chan) { _chan = chan; }
public void setKey(SelectionKey key) { _conKey = key; }
+
public boolean isInbound() { return _isInbound; }
public boolean isEstablished() { return _establishState.isComplete(); }
@@ -261,9 +294,10 @@ public class NTCPConnection implements Closeable {
}
/**
- * Only valid during establishment; null later
+ * Only valid during establishment;
+ * replaced with EstablishState.VERIFIED or FAILED afterward
*/
- public EstablishState getEstablishState() { return _establishState; }
+ EstablishState getEstablishState() { return _establishState; }
/**
* Only valid for outbound; null for inbound
@@ -274,17 +308,23 @@ public class NTCPConnection implements Closeable {
* Valid for outbound; valid for inbound after handshake
*/
public RouterIdentity getRemotePeer() { return _remotePeer; }
+
+ /**
+ * Valid for outbound; valid for inbound after handshake
+ */
public void setRemotePeer(RouterIdentity ident) { _remotePeer = ident; }
/**
- * We are Bob.
+ * We are Bob. NTCP1 only.
+ *
+ * Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
*
* @param clockSkew OUR clock minus ALICE's clock in seconds (may be negative, obviously, but |val| should
* be under 1 minute)
- * @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt
- * @param prevReadEnd 16 or more bytes, last 16 bytes copied
+ * @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt, the write AES IV
+ * @param prevReadEnd 16 or more bytes, last 16 bytes copied as the read AES IV
*/
- public void finishInboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
+ void finishInboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
NTCPConnection toClose = locked_finishInboundEstablishment(key, clockSkew, prevWriteEnd, prevReadEnd);
if (toClose != null) {
if (_log.shouldLog(Log.DEBUG))
@@ -296,20 +336,27 @@ public class NTCPConnection implements Closeable {
}
/**
- * We are Bob.
+ * We are Bob. NTCP1 only.
*
* @param clockSkew OUR clock minus ALICE's clock in seconds (may be negative, obviously, but |val| should
* be under 1 minute)
- * @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt
- * @param prevReadEnd 16 or more bytes, last 16 bytes copied
+ * @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt, the write AES IV
+ * @param prevReadEnd 16 or more bytes, last 16 bytes copied as the read AES IV
* @return old conn to be closed by caller, or null
*/
private synchronized NTCPConnection locked_finishInboundEstablishment(
SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
+ if (_establishState == EstablishBase.VERIFIED) {
+ IllegalStateException ise = new IllegalStateException("Already finished on " + this);
+ _log.error("Already finished", ise);
+ throw ise;
+ }
+ byte[] prevReadBlock = new byte[BLOCK_SIZE];
+ System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, prevReadBlock, 0, BLOCK_SIZE);
+ _curReadState = new NTCP1ReadState(prevReadBlock);
_sessionKey = key;
_clockSkew = clockSkew;
_prevWriteEnd = prevWriteEnd;
- System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, _prevReadBlock, 0, BLOCK_SIZE);
_establishedOn = _context.clock().now();
NTCPConnection rv = _transport.inboundEstablished(this);
_nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY);
@@ -337,21 +384,20 @@ public class NTCPConnection implements Closeable {
public int getMessagesReceived() { return _messagesRead.get(); }
public int getOutboundQueueSize() {
- int queued;
- synchronized(_outbound) {
- queued = _outbound.size();
- if (getCurrentOutbound() != null)
- queued++;
+ int queued = _outbound.size();
+ synchronized(_currentOutbound) {
+ queued += _currentOutbound.size();
}
return queued;
}
-
- private OutNetMessage getCurrentOutbound() {
- synchronized(_outbound) {
- return _currentOutbound;
+
+ /** @since 0.9.36 */
+ private boolean hasCurrentOutbound() {
+ synchronized(_currentOutbound) {
+ return ! _currentOutbound.isEmpty();
}
}
-
+
/** @return milliseconds */
public long getTimeSinceSend() { return _context.clock().now()-_lastSendTime; }
@@ -367,6 +413,24 @@ public class NTCPConnection implements Closeable {
*/
public long getCreated() { return _created; }
+ /**
+ * The NTCP2 version, for the console.
+ * For outbound, will not change.
+ * For inbound, defaults to 1, may change to 2 after establishment.
+ *
+ * @return the version, 1 or 2
+ * @since 0.9.36
+ */
+ public int getVersion() { return _version; }
+
+ /**
+ * Set version 2 from InboundEstablishState.
+ * Just for logging, so we know before finishInboundEstablish() is called.
+ *
+ * @since 0.9.36
+ */
+ public void setVersion(int ver) { _version = ver; }
+
/**
* Sets to true.
* @since 0.9.24
@@ -382,8 +446,8 @@ public class NTCPConnection implements Closeable {
* workaround for EventPumper
* @since 0.8.12
*/
- public void clearZeroRead() {
- _consecutiveZeroReads = 0;
+ void clearZeroRead() {
+ _consecutiveZeroReads.set(0);
}
/**
@@ -391,8 +455,8 @@ public class NTCPConnection implements Closeable {
* @return value after incrementing
* @since 0.8.12
*/
- public int gotZeroRead() {
- return ++_consecutiveZeroReads;
+ int gotZeroRead() {
+ return _consecutiveZeroReads.incrementAndGet();
}
public boolean isClosed() { return _closed.get(); }
@@ -404,8 +468,13 @@ public class NTCPConnection implements Closeable {
_log.logCloseLoop("NTCPConnection", this);
return;
}
- if (_log.shouldLog(Log.INFO))
+ if (_version == 2) {
+ // for debugging
+ if (_log.shouldWarn())
+ _log.warn("Closing connection " + toString(), new Exception("cause"));
+ } else if (_log.shouldLog(Log.INFO)) {
_log.info("Closing connection " + toString(), new Exception("cause"));
+ }
NTCPConnection toClose = locked_close(allowRequeue);
if (toClose != null && toClose != this) {
if (_log.shouldLog(Log.WARN))
@@ -420,7 +489,7 @@ public class NTCPConnection implements Closeable {
* @param e may be null
* @since 0.9.16
*/
- public void closeOnTimeout(String cause, Exception e) {
+ void closeOnTimeout(String cause, Exception e) {
EstablishState es = _establishState;
close();
es.close(cause, e);
@@ -457,13 +526,29 @@ public class NTCPConnection implements Closeable {
List pending = new ArrayList();
//_outbound.drainAllTo(pending);
_outbound.drainTo(pending);
- for (OutNetMessage msg : pending)
+ synchronized(_currentOutbound) {
+ if (!_currentOutbound.isEmpty())
+ pending.addAll(_currentOutbound);
+ _currentOutbound.clear();
+ }
+ for (OutNetMessage msg : pending) {
_transport.afterSend(msg, false, allowRequeue, msg.getLifetime());
-
- OutNetMessage msg = getCurrentOutbound();
- if (msg != null)
- _transport.afterSend(msg, false, allowRequeue, msg.getLifetime());
-
+ }
+ // zero out everything we can
+ if (_curReadState != null) {
+ _curReadState.destroy();
+ _curReadState = null;
+ }
+ if (_sender != null) {
+ _sender.destroy();
+ _sender = null;
+ }
+ _sendSipk1 = 0;
+ _sendSipk2 = 0;
+ if (_sendSipIV != null) {
+ Arrays.fill(_sendSipIV, (byte) 0);
+ _sendSipIV = null;
+ }
return old;
}
@@ -477,8 +562,7 @@ public class NTCPConnection implements Closeable {
_transport.afterSend(msg, false, false, msg.getLifetime());
return;
}
- boolean noOutbound = (getCurrentOutbound() == null);
- if (isEstablished() && noOutbound)
+ if (isEstablished() && !hasCurrentOutbound())
_transport.getWriter().wantsWrite(this, "enqueued");
}
@@ -493,13 +577,19 @@ public class NTCPConnection implements Closeable {
if (_outbound.isBacklogged()) { // bloody arbitrary. well, its half the average message lifetime...
int size = _outbound.size();
if (_log.shouldLog(Log.WARN)) {
- int writeBufs = _writeBufs.size();
- boolean currentOutboundSet = getCurrentOutbound() != null;
+ int writeBufs = _writeBufs.size();
+ boolean currentOutboundSet;
+ long seq;
+ synchronized(_currentOutbound) {
+ currentOutboundSet = !_currentOutbound.isEmpty();
+ seq = currentOutboundSet ? _currentOutbound.get(0).getSeqNum() : -1;
+ }
try {
_log.warn("Too backlogged: size is " + size
- + ", wantsWrite? " + (0 != (_conKey.interestOps()&SelectionKey.OP_WRITE))
- + ", currentOut set? " + currentOutboundSet
- + ", writeBufs: " + writeBufs + " on " + toString());
+ + ", wantsWrite? " + (0 != (_conKey.interestOps()&SelectionKey.OP_WRITE))
+ + ", currentOut set? " + currentOutboundSet
+ + ", id: " + seq
+ + ", writeBufs: " + writeBufs + " on " + toString());
} catch (RuntimeException e) {} // java.nio.channels.CancelledKeyException
}
return true;
@@ -509,9 +599,30 @@ public class NTCPConnection implements Closeable {
}
/**
- * Inject a DatabaseStoreMessage with our RouterInfo
+ * Inject a DatabaseStoreMessage with our RouterInfo. NTCP 1 or 2.
+ *
+ * Externally, this is only called by NTCPTransport for outbound cons,
+ * before the con is established, but we know what version it is.
+ *
+ * Internally, may be called for outbound or inbound, but only after the
+ * con is established, so we know what the version is.
*/
- public void enqueueInfoMessage() {
+ void enqueueInfoMessage() {
+ if (_version == 1) {
+ enqueueInfoMessageNTCP1();
+ // may change to 2 for inbound
+ } else if (_isInbound) {
+ // TODO or if outbound and it's not right at the beginning
+ // TODO flood
+ sendOurRouterInfo(false);
+ }
+ // don't need to send for NTCP 2 outbound, it's in msg 3
+ }
+
+ /**
+ * Inject a DatabaseStoreMessage with our RouterInfo. NTCP 1 only.
+ */
+ private void enqueueInfoMessageNTCP1() {
int priority = INFO_PRIORITY;
if (_log.shouldLog(Log.INFO))
_log.info("SENDING INFO message pri. " + priority + ": " + toString());
@@ -524,53 +635,50 @@ public class NTCPConnection implements Closeable {
}
/**
- * We are Alice.
+ * We are Alice. NTCP1 only.
+ *
+ * Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
*
* @param clockSkew OUR clock minus BOB's clock in seconds (may be negative, obviously, but |val| should
* be under 1 minute)
* @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt
* @param prevReadEnd 16 or more bytes, last 16 bytes copied
*/
- public synchronized void finishOutboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
- if (_log.shouldLog(Log.DEBUG))
- _log.debug("outbound established (key=" + key + " skew=" + clockSkew + " prevWriteEnd=" + Base64.encode(prevWriteEnd) + ")");
+ synchronized void finishOutboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
+ if (_establishState == EstablishBase.VERIFIED) {
+ IllegalStateException ise = new IllegalStateException("Already finished on " + this);
+ _log.error("Already finished", ise);
+ throw ise;
+ }
+ byte[] prevReadBlock = new byte[BLOCK_SIZE];
+ System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, prevReadBlock, 0, BLOCK_SIZE);
+ _curReadState = new NTCP1ReadState(prevReadBlock);
_sessionKey = key;
_clockSkew = clockSkew;
_prevWriteEnd = prevWriteEnd;
- System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, _prevReadBlock, 0, BLOCK_SIZE);
if (_log.shouldLog(Log.DEBUG))
- _log.debug("Outbound established, prevWriteEnd: " + Base64.encode(prevWriteEnd) + " prevReadEnd: " + Base64.encode(prevReadEnd));
+ _log.debug("outbound established (key=" + key + " skew=" + clockSkew +
+ " prevWriteEnd: " + Base64.encode(prevWriteEnd) + " prevReadBlock: " + Base64.encode(prevReadBlock));
_establishedOn = _context.clock().now();
_establishState = EstablishBase.VERIFIED;
_transport.markReachable(getRemotePeer().calculateHash(), false);
- boolean msgs = !_outbound.isEmpty();
_nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY);
_nextInfoTime = _establishedOn + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
- if (msgs)
+ if (!_outbound.isEmpty())
_transport.getWriter().wantsWrite(this, "outbound established");
}
/**
- * prepare the next i2np message for transmission. this should be run from
- * the Writer thread pool.
+ * Prepare the next I2NP message for transmission. This should be run from
+ * the Writer thread pool. NTCP 1 or 2.
+ *
+ * This is the entry point as called from Writer.Runner.run()
*
* @param prep an instance of PrepBuffer to use as scratch space
*
*/
synchronized void prepareNextWrite(PrepBuffer prep) {
- prepareNextWriteFast(prep);
- }
-
- /**
- * prepare the next i2np message for transmission. this should be run from
- * the Writer thread pool.
- *
- * Caller must synchronize.
- * @param buf a PrepBuffer to use as scratch space
- *
- */
- private void prepareNextWriteFast(PrepBuffer buf) {
if (_closed.get())
return;
// Must be established or else session key is null and we can't encrypt
@@ -580,29 +688,48 @@ public class NTCPConnection implements Closeable {
if (!isEstablished()) {
return;
}
-
+ if (_version == 1)
+ prepareNextWriteFast(prep);
+ else
+ prepareNextWriteNTCP2(prep);
+ }
+
+ /**
+ * Prepare the next I2NP message for transmission. This should be run from
+ * the Writer thread pool. NTCP 1 only.
+ *
+ * Caller must synchronize.
+ * @param buf a PrepBuffer to use as scratch space
+ *
+ */
+ private void prepareNextWriteFast(PrepBuffer buf) {
long now = _context.clock().now();
if (_nextMetaTime <= now) {
sendMeta();
_nextMetaTime = now + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY / 2);
}
- OutNetMessage msg = null;
- // this is synchronized only for _currentOutbound
- // Todo: figure out how to remove the synchronization
- synchronized (_outbound) {
- if (_currentOutbound != null) {
+ OutNetMessage msg;
+ synchronized (_currentOutbound) {
+ if (!_currentOutbound.isEmpty()) {
if (_log.shouldLog(Log.INFO))
- _log.info("attempt for multiple outbound messages with " + System.identityHashCode(_currentOutbound) + " already waiting and " + _outbound.size() + " queued");
+ _log.info("attempt for multiple outbound messages with " + _currentOutbound.size() + " already waiting and " + _outbound.size() + " queued");
return;
}
+ while (true) {
msg = _outbound.poll();
if (msg == null)
return;
- _currentOutbound = msg;
+ if (msg.getExpiration() >= now)
+ break;
+ if (_log.shouldWarn())
+ _log.warn("dropping message expired on queue: " + msg + " on " + this);
+ _transport.afterSend(msg, false, false, msg.getLifetime());
+ }
+ _currentOutbound.add(msg);
}
-
- bufferedPrepare(msg,buf);
+
+ bufferedPrepare(msg, buf);
_context.aes().encrypt(buf.unencrypted, 0, buf.encrypted, 0, _sessionKey, _prevWriteEnd, 0, buf.unencryptedLength);
System.arraycopy(buf.encrypted, buf.encrypted.length-16, _prevWriteEnd, 0, _prevWriteEnd.length);
_transport.getPumper().wantsWrite(this, buf.encrypted);
@@ -626,17 +753,15 @@ public class NTCPConnection implements Closeable {
*/
private void bufferedPrepare(OutNetMessage msg, PrepBuffer buf) {
I2NPMessage m = msg.getMessage();
- buf.baseLength = m.toByteArray(buf.base);
- int sz = buf.baseLength;
+ // 2 offset for size
+ int sz = m.toByteArray(buf.unencrypted, 2) - 2;
int min = 2 + sz + 4;
int rem = min % 16;
int padding = 0;
if (rem > 0)
padding = 16 - rem;
-
buf.unencryptedLength = min+padding;
DataHelper.toLong(buf.unencrypted, 0, 2, sz);
- System.arraycopy(buf.base, 0, buf.unencrypted, 2, buf.baseLength);
if (padding > 0) {
_context.random().nextBytes(buf.unencrypted, 2+sz, padding);
}
@@ -657,33 +782,252 @@ public class NTCPConnection implements Closeable {
buf.encrypted = new byte[buf.unencryptedLength];
}
- public static class PrepBuffer {
+ static class PrepBuffer {
final byte unencrypted[];
int unencryptedLength;
- final byte base[];
- int baseLength;
final Adler32 crc;
byte encrypted[];
public PrepBuffer() {
unencrypted = new byte[BUFFER_SIZE];
- base = new byte[BUFFER_SIZE];
crc = new Adler32();
}
public void init() {
unencryptedLength = 0;
- baseLength = 0;
encrypted = null;
crc.reset();
}
}
+
+ /**
+ * Prepare the next I2NP message for transmission. This should be run from
+ * the Writer thread pool.
+ *
+ * Caller must synchronize.
+ *
+ * @param buf we use buf.enencrypted only
+ * @since 0.9.36
+ */
+ private void prepareNextWriteNTCP2(PrepBuffer buf) {
+ int size = OutboundNTCP2State.MAC_SIZE;
+ List blocks = new ArrayList(4);
+ long now = _context.clock().now();
+ synchronized (_currentOutbound) {
+ if (!_currentOutbound.isEmpty()) {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("attempt for multiple outbound messages with " + _currentOutbound.size() + " already waiting and " + _outbound.size() + " queued");
+ return;
+ }
+ OutNetMessage msg;
+ while (true) {
+ msg = _outbound.poll();
+ if (msg == null)
+ return;
+ if (msg.getExpiration() >= now)
+ break;
+ if (_log.shouldWarn())
+ _log.warn("dropping message expired on queue: " + msg + " on " + this);
+ _transport.afterSend(msg, false, false, msg.getLifetime());
+ }
+ _currentOutbound.add(msg);
+ // don't make combined msgs too big to minimize latency
+ final int MAX_MSG_SIZE = 5000;
+ I2NPMessage m = msg.getMessage();
+ Block block = new NTCP2Payload.I2NPBlock(m);
+ blocks.add(block);
+ size += block.getTotalLength();
+ // now add more (maybe)
+ if (size < MAX_MSG_SIZE) {
+ // keep adding as long as we will be under 5 KB
+ while (true) {
+ msg = _outbound.peek();
+ if (msg == null)
+ break;
+ m = msg.getMessage();
+ int msz = m.getMessageSize() - 7;
+ if (size + msz > MAX_MSG_SIZE)
+ break;
+ OutNetMessage msg2 = _outbound.poll();
+ if (msg2 == null)
+ break;
+ if (msg2 != msg) {
+ // if it wasn't the one we sized, put it back
+ _outbound.offer(msg2);
+ break;
+ }
+ if (msg.getExpiration() >= now) {
+ block = new NTCP2Payload.I2NPBlock(m);
+ blocks.add(block);
+ size += NTCP2Payload.BLOCK_HEADER_SIZE + msz;
+ } else {
+ if (_log.shouldWarn())
+ _log.warn("dropping message expired on queue: " + msg + " on " + this);
+ _transport.afterSend(msg, false, false, msg.getLifetime());
+ }
+ }
+ }
+ }
+ if (_nextMetaTime <= now && size + (NTCP2Payload.BLOCK_HEADER_SIZE + 4) <= BUFFER_SIZE) {
+ Block block = new NTCP2Payload.DateTimeBlock(_context);
+ blocks.add(block);
+ size += block.getTotalLength();
+ _nextMetaTime = now + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY / 2);
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Sending NTCP2 datetime block");
+ }
+ // 1024 is an estimate, do final check below
+ if (_nextInfoTime <= now && size + 1024 <= BUFFER_SIZE) {
+ RouterInfo ri = _context.router().getRouterInfo();
+ Block block = new NTCP2Payload.RIBlock(ri, false);
+ int sz = block.getTotalLength();
+ if (size + sz <= BUFFER_SIZE) {
+ blocks.add(block);
+ size += sz;
+ _nextInfoTime = now + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
+ if (_log.shouldLog(Log.INFO))
+ _log.info("SENDING NTCP2 RI block");
+ } // else wait until next time
+ }
+ int availForPad = BUFFER_SIZE - (size + NTCP2Payload.BLOCK_HEADER_SIZE);
+ if (availForPad > 0) {
+ // what we want to send, calculated in proportion to data size
+ int minSend = (int) (size * _paddingConfig.getSendMin());
+ int maxSend = (int) (size * _paddingConfig.getSendMax());
+ // the absolute min and max we can send
+ int min = Math.min(minSend, availForPad);
+ int max = Math.min(maxSend, availForPad);
+ int range = max - min;
+ if (range < MIN_PADDING_RANGE) {
+ // reduce min to enforce minimum range if possible
+ min = Math.max(0, min - (MIN_PADDING_RANGE - range));
+ range = max - min;
+ }
+ int padlen = min;
+ if (range > 0)
+ padlen += _context.random().nextInt(1 + range);
+ if (_log.shouldWarn())
+ _log.warn("Padding params:" +
+ " size: " + size +
+ " avail: " + availForPad +
+ " minSend: " + minSend +
+ " maxSend: " + maxSend +
+ " min: " + min +
+ " max: " + max +
+ " range: " + range +
+ " padlen: " + padlen);
+ // all zeros is fine here
+ //Block block = new NTCP2Payload.PaddingBlock(_context, padlen);
+ Block block = new NTCP2Payload.PaddingBlock(padlen);
+ blocks.add(block);
+ size += block.getTotalLength();
+ }
+ sendNTCP2(buf.unencrypted, blocks);
+ }
+
+ /**
+ * NTCP2 only
+ *
+ * @since 0.9.36
+ */
+ private void sendOurRouterInfo(boolean shouldFlood) {
+ sendRouterInfo(_context.router().getRouterInfo(), shouldFlood);
+ }
+
+ /**
+ * NTCP2 only
+ *
+ * @since 0.9.36
+ */
+ private void sendRouterInfo(RouterInfo ri, boolean shouldFlood) {
+ // no synch needed, sendNTCP2() is synched
+ if (_log.shouldWarn())
+ _log.warn("Sending router info for: " + ri.getHash() + " flood? " + shouldFlood);
+ List blocks = new ArrayList(2);
+ int plen = 2;
+ Block block = new NTCP2Payload.RIBlock(ri, shouldFlood);
+ plen += block.getTotalLength();
+ blocks.add(block);
+ int padlen = 1 + _context.random().nextInt(PADDING_MAX);
+ // all zeros is fine here
+ //block = new NTCP2Payload.PaddingBlock(_context, padlen);
+ block = new NTCP2Payload.PaddingBlock(padlen);
+ plen += block.getTotalLength();
+ blocks.add(block);
+ byte[] tmp = new byte[plen];
+ sendNTCP2(tmp, blocks);
+ }
+
+ /**
+ * NTCP2 only
+ *
+ * @since 0.9.36
+ */
+ private void sendTermination(int reason, int validFramesRcvd) {
+ // TODO add param to clear queues?
+ // no synch needed, sendNTCP2() is synched
+ if (_log.shouldWarn())
+ _log.warn("Sending termination, reason: " + reason + ", vaild frames rcvd: " + validFramesRcvd);
+ List blocks = new ArrayList(2);
+ int plen = 2;
+ Block block = new NTCP2Payload.TerminationBlock(reason, validFramesRcvd);
+ plen += block.getTotalLength();
+ blocks.add(block);
+ int padlen = 1 + _context.random().nextInt(PADDING_MAX);
+ // all zeros is fine here
+ //block = new NTCP2Payload.PaddingBlock(_context, padlen);
+ block = new NTCP2Payload.PaddingBlock(padlen);
+ plen += block.getTotalLength();
+ blocks.add(block);
+ byte[] tmp = new byte[plen];
+ sendNTCP2(tmp, blocks);
+ }
+
+ /**
+ * This constructs the payload from the blocks, using the
+ * tmp byte array, then encrypts the payload and
+ * passes it to the pumper for writing.
+ *
+ * @param tmp to be used for output of NTCP2Payload.writePayload(),
+ * must have room for 2 byte length and block output
+ * @since 0.9.36
+ */
+ private synchronized void sendNTCP2(byte[] tmp, List blocks) {
+ int payloadlen = NTCP2Payload.writePayload(tmp, 0, blocks);
+ int framelen = payloadlen + OutboundNTCP2State.MAC_SIZE;
+ // TODO use a buffer
+ byte[] enc = new byte[2 + framelen];
+ try {
+ _sender.encryptWithAd(null, tmp, 0, enc, 2, payloadlen);
+ } catch (GeneralSecurityException gse) {
+ // TODO anything else?
+ _log.error("data enc", gse);
+ return;
+ }
+
+ // siphash ^ len
+ long sipIV = SipHashInline.hash24(_sendSipk1, _sendSipk2, _sendSipIV);
+ enc[0] = (byte) ((framelen >> 8) ^ (sipIV >> 8));
+ enc[1] = (byte) (framelen ^ sipIV);
+ if (_log.shouldWarn()) {
+ StringBuilder buf = new StringBuilder(256);
+ buf.append("Sending ").append(blocks.size())
+ .append(" blocks in ").append(framelen)
+ .append(" byte NTCP2 frame:");
+ for (int i = 0; i < blocks.size(); i++) {
+ buf.append("\n ").append(i).append(": ").append(blocks.get(i).toString());
+ }
+ _log.warn(buf.toString());
+ }
+ _transport.getPumper().wantsWrite(this, enc);
+ toLong8LE(_sendSipIV, 0, sipIV);
+ }
/**
* async callback after the outbound connection was completed (this should NOT block,
* as it occurs in the selector thread)
*/
- public void outboundConnected() {
+ void outboundConnected() {
_conKey.interestOps(_conKey.interestOps() | SelectionKey.OP_READ);
// schedule up the beginning of our handshaking by calling prepareNextWrite on the
// writer thread pool
@@ -748,14 +1092,14 @@ public class NTCPConnection implements Closeable {
* the buffer (not copy) and register ourselves to be notified when the
* contents have been fully allocated
*/
- public void queuedRecv(ByteBuffer buf, FIFOBandwidthLimiter.Request req) {
+ void queuedRecv(ByteBuffer buf, FIFOBandwidthLimiter.Request req) {
req.attach(buf);
req.setCompleteListener(_inboundListener);
addIBRequest(req);
}
/** ditto for writes */
- public void queuedWrite(ByteBuffer buf, FIFOBandwidthLimiter.Request req) {
+ void queuedWrite(ByteBuffer buf, FIFOBandwidthLimiter.Request req) {
req.attach(buf);
req.setCompleteListener(_outboundListener);
addOBRequest(req);
@@ -767,24 +1111,31 @@ public class NTCPConnection implements Closeable {
* to do with as it pleases BUT it should eventually copy out the data
* and call EventPumper.releaseBuf().
*/
- public void recv(ByteBuffer buf) {
- _bytesReceived += buf.remaining();
+ void recv(ByteBuffer buf) {
+ if (isClosed()) {
+ if (_log.shouldWarn())
+ _log.warn("recv() on closed con");
+ return;
+ }
+ synchronized(this) {
+ _bytesReceived += buf.remaining();
+ updateStats();
+ }
_readBufs.offer(buf);
_transport.getReader().wantsRead(this);
- updateStats();
}
/**
* The contents of the buffer have been encrypted / padded / etc and have
* been fully allocated for the bandwidth limiter.
*/
- public void write(ByteBuffer buf) {
+ void write(ByteBuffer buf) {
_writeBufs.offer(buf);
_transport.getPumper().wantsWrite(this);
}
/** @return null if none available */
- public ByteBuffer getNextReadBuf() {
+ ByteBuffer getNextReadBuf() {
return _readBufs.poll();
}
@@ -792,57 +1143,66 @@ public class NTCPConnection implements Closeable {
* Replaces getWriteBufCount()
* @since 0.8.12
*/
- public boolean isWriteBufEmpty() {
+ boolean isWriteBufEmpty() {
return _writeBufs.isEmpty();
}
/** @return null if none available */
- public ByteBuffer getNextWriteBuf() {
+ ByteBuffer getNextWriteBuf() {
return _writeBufs.peek(); // not remove! we removeWriteBuf afterwards
}
/**
* Remove the buffer, which _should_ be the one at the head of _writeBufs
*/
- public void removeWriteBuf(ByteBuffer buf) {
- _bytesSent += buf.capacity();
- OutNetMessage msg = null;
- boolean clearMessage = false;
- if (_sendingMeta && (buf.capacity() == _meta.length)) {
- _sendingMeta = false;
- } else {
- clearMessage = true;
+ void removeWriteBuf(ByteBuffer buf) {
+ // never clear OutNetMessages during establish phase
+ boolean clearMessage = isEstablished();
+ synchronized(this) {
+ _bytesSent += buf.capacity();
+ if (_sendingMeta && (buf.capacity() == META_SIZE)) {
+ _sendingMeta = false;
+ clearMessage = false;
+ }
+ updateStats();
}
_writeBufs.remove(buf);
if (clearMessage) {
+ List msgs = null;
// see synchronization comments in prepareNextWriteFast()
- synchronized (_outbound) {
- if (_currentOutbound != null) {
- msg = _currentOutbound;
- _currentOutbound = null;
+ synchronized (_currentOutbound) {
+ if (!_currentOutbound.isEmpty()) {
+ msgs = new ArrayList(_currentOutbound);
+ _currentOutbound.clear();
}
}
- if (msg != null) {
+ // push through the bw limiter to reach _writeBufs
+ if (!_outbound.isEmpty())
+ _transport.getWriter().wantsWrite(this, "write completed");
+ if (msgs != null) {
_lastSendTime = _context.clock().now();
- _context.statManager().addRateData("ntcp.sendTime", msg.getSendTime());
- if (_log.shouldLog(Log.DEBUG)) {
- _log.debug("I2NP message " + _messagesWritten + "/" + msg.getMessageId() + " sent after "
- + msg.getSendTime() + "/"
- + msg.getLifetime()
- + " with " + buf.capacity() + " bytes (uid=" + System.identityHashCode(msg)+" on " + toString() + ")");
+ // stats once is fine for all of them
+ _context.statManager().addRateData("ntcp.sendTime", msgs.get(0).getSendTime());
+ for (OutNetMessage msg : msgs) {
+ if (_log.shouldLog(Log.DEBUG)) {
+ _log.debug("I2NP message " + _messagesWritten + "/" + msg.getMessageId() + " sent after "
+ + msg.getSendTime() + "/"
+ + msg.getLifetime()
+ + " with " + buf.capacity() + " bytes (uid=" + System.identityHashCode(msg)+" on " + toString() + ")");
+ }
+ _transport.sendComplete(msg);
}
- _messagesWritten.incrementAndGet();
- _transport.sendComplete(msg);
+ _messagesWritten.addAndGet(msgs.size());
}
} else {
+ // push through the bw limiter to reach _writeBufs
+ if (!_outbound.isEmpty())
+ _transport.getWriter().wantsWrite(this, "write completed");
if (_log.shouldLog(Log.INFO))
_log.info("I2NP meta message sent completely");
+ // need to increment as EventPumper will close conn if not completed
+ _messagesWritten.incrementAndGet();
}
-
- if (getOutboundQueueSize() > 0) // push through the bw limiter to reach _writeBufs
- _transport.getWriter().wantsWrite(this, "write completed");
-
- updateStats();
}
private long _bytesReceived;
@@ -854,13 +1214,13 @@ public class NTCPConnection implements Closeable {
private float _sendBps;
private float _recvBps;
- public float getSendRate() { return _sendBps; }
- public float getRecvRate() { return _recvBps; }
+ public synchronized float getSendRate() { return _sendBps; }
+ public synchronized float getRecvRate() { return _recvBps; }
/**
* Stats only for console
*/
- private void updateStats() {
+ private synchronized void updateStats() {
long now = _context.clock().now();
long time = now - _lastRateUpdated;
// If enough time has passed...
@@ -892,151 +1252,76 @@ public class NTCPConnection implements Closeable {
* The NTCP connection now owns the buffer
* BUT it must copy out the data
* as reader will call EventPumper.releaseBuf().
+ *
+ * This is the entry point as called from Reader.processRead()
*/
synchronized void recvEncryptedI2NP(ByteBuffer buf) {
- // hasArray() is false for direct buffers, at least on my system...
- if (_curReadBlockIndex == 0 && buf.hasArray()) {
- // fast way
- int tot = buf.remaining();
- if (tot >= 32 && tot % 16 == 0) {
- recvEncryptedFast(buf);
- return;
- }
- }
-
- while (buf.hasRemaining() && !_closed.get()) {
- int want = Math.min(buf.remaining(), BLOCK_SIZE - _curReadBlockIndex);
- if (want > 0) {
- buf.get(_curReadBlock, _curReadBlockIndex, want);
- _curReadBlockIndex += want;
- }
- if (_curReadBlockIndex >= BLOCK_SIZE) {
- // cbc
- _context.aes().decryptBlock(_curReadBlock, 0, _sessionKey, _decryptBlockBuf, 0);
- for (int i = 0; i < BLOCK_SIZE; i++) {
- _decryptBlockBuf[i] ^= _prevReadBlock[i];
- }
- boolean ok = recvUnencryptedI2NP();
- if (!ok) {
- if (_log.shouldLog(Log.INFO))
- _log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data");
- _context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
- return;
- }
- byte swap[] = _prevReadBlock;
- _prevReadBlock = _curReadBlock;
- _curReadBlock = swap;
- _curReadBlockIndex = 0;
- }
- }
- }
-
- /**
- * Decrypt directly out of the ByteBuffer instead of copying the bytes
- * 16 at a time to the _curReadBlock / _prevReadBlock flip buffers.
- *
- * More efficient but can only be used if buf.hasArray == true AND
- * _curReadBlockIndex must be 0 and buf.getRemaining() % 16 must be 0
- * and buf.getRemaining() must be >= 16.
- * All this is true for most buffers.
- * In theory this could be fixed up to handle the other cases too but that's hard.
- * Caller must synchronize!
- * @since 0.8.12
- */
- private void recvEncryptedFast(ByteBuffer buf) {
- byte[] array = buf.array();
- int pos = buf.arrayOffset();
- int end = pos + buf.remaining();
- boolean first = true;
-
- for ( ; pos < end && !_closed.get(); pos += BLOCK_SIZE) {
- _context.aes().decryptBlock(array, pos, _sessionKey, _decryptBlockBuf, 0);
- if (first) {
- for (int i = 0; i < BLOCK_SIZE; i++) {
- _decryptBlockBuf[i] ^= _prevReadBlock[i];
- }
- first = false;
- } else {
- int start = pos - BLOCK_SIZE;
- for (int i = 0; i < BLOCK_SIZE; i++) {
- _decryptBlockBuf[i] ^= array[start + i];
- }
- }
- boolean ok = recvUnencryptedI2NP();
- if (!ok) {
- if (_log.shouldLog(Log.INFO))
- _log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data");
- _context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
- return;
- }
- }
- // ...and copy to _prevReadBlock the last time
- System.arraycopy(array, end - BLOCK_SIZE, _prevReadBlock, 0, BLOCK_SIZE);
- }
-
- /**
- * Append the next 16 bytes of cleartext to the read state.
- * _decryptBlockBuf contains another cleartext block of I2NP to parse.
- * Caller must synchronize!
- * @return success
- */
- private boolean recvUnencryptedI2NP() {
- _curReadState.receiveBlock(_decryptBlockBuf);
- // FIXME move check to ReadState; must we close? possible attack vector?
- if (_curReadState.getSize() > BUFFER_SIZE) {
- if (_log.shouldLog(Log.WARN))
- _log.warn("I2NP message too big - size: " + _curReadState.getSize() + " Dropping " + toString());
- _context.statManager().addRateData("ntcp.corruptTooLargeI2NP", _curReadState.getSize());
- close();
- return false;
- } else {
- return true;
- }
+ if (_curReadState == null)
+ throw new IllegalStateException("not established");
+ _curReadState.receive(buf);
}
/*
* One special case is a metadata message where the sizeof(data) is 0. In
* that case, the unencrypted message is encoded as:
+ *
+ *
* +-------+-------+-------+-------+-------+-------+-------+-------+
* | 0 | timestamp in seconds | uninterpreted
* +-------+-------+-------+-------+-------+-------+-------+-------+
* uninterpreted | adler checksum of sz+data+pad |
* +-------+-------+-------+-------+-------+-------+-------+-------+
+ *
*
+ * Caller must synch
+ *
+ * @param unencrypted 16 bytes starting at off
+ * @param off the offset
*/
- private void readMeta(byte unencrypted[]) {
- long ourTs = (_context.clock().now() + 500) / 1000;
- long ts = DataHelper.fromLong(unencrypted, 2, 4);
+ private void readMeta(byte unencrypted[], int off) {
Adler32 crc = new Adler32();
- crc.update(unencrypted, 0, unencrypted.length-4);
+ crc.update(unencrypted, off, META_SIZE - 4);
long expected = crc.getValue();
- long read = DataHelper.fromLong(unencrypted, unencrypted.length-4, 4);
+ long read = DataHelper.fromLong(unencrypted, off + META_SIZE - 4, 4);
if (read != expected) {
if (_log.shouldLog(Log.WARN))
_log.warn("I2NP metadata message had a bad CRC value");
_context.statManager().addRateData("ntcp.corruptMetaCRC", 1);
close();
return;
- } else {
- long newSkew = (ourTs - ts);
- if (Math.abs(newSkew*1000) > Router.CLOCK_FUDGE_FACTOR) {
- if (_log.shouldLog(Log.WARN))
- _log.warn("Peer's skew jumped too far (from " + _clockSkew + " s to " + newSkew + " s): " + toString());
- _context.statManager().addRateData("ntcp.corruptSkew", newSkew);
- close();
- return;
- }
- _context.statManager().addRateData("ntcp.receiveMeta", newSkew);
- if (_log.shouldLog(Log.DEBUG))
- _log.debug("Received NTCP metadata, old skew of " + _clockSkew + " s, new skew of " + newSkew + "s.");
- // FIXME does not account for RTT
- _clockSkew = newSkew;
}
+ long ts = DataHelper.fromLong(unencrypted, off + 2, 4);
+ receiveTimestamp(ts);
+ }
+
+ /**
+ * Handle a received timestamp, NTCP 1 or 2.
+ * Caller must synch
+ *
+ * @param ts his timestamp in seconds, NOT ms
+ * @since 0.9.36 pulled out of readMeta() above
+ */
+ private void receiveTimestamp(long ts) {
+ long ourTs = (_context.clock().now() + 500) / 1000;
+ long newSkew = (ourTs - ts);
+ if (Math.abs(newSkew*1000) > Router.CLOCK_FUDGE_FACTOR) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Peer's skew jumped too far (from " + _clockSkew + " s to " + newSkew + " s): " + toString());
+ _context.statManager().addRateData("ntcp.corruptSkew", newSkew);
+ close();
+ return;
+ }
+ _context.statManager().addRateData("ntcp.receiveMeta", newSkew);
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Received NTCP metadata, old skew of " + _clockSkew + " s, new skew of " + newSkew + "s.");
+ // FIXME does not account for RTT
+ _clockSkew = newSkew;
}
/**
* One special case is a metadata message where the sizeof(data) is 0. In
* that case, the unencrypted message is encoded as:
+ *
*
* +-------+-------+-------+-------+-------+-------+-------+-------+
* | 0 | timestamp in seconds | uninterpreted
@@ -1044,24 +1329,24 @@ public class NTCPConnection implements Closeable {
* uninterpreted | adler checksum of sz+data+pad |
* +-------+-------+-------+-------+-------+-------+-------+-------+
*
+ *
+ * Caller must synchronize.
*/
private void sendMeta() {
- byte encrypted[] = new byte[_meta.length];
- synchronized (_meta) {
- DataHelper.toLong(_meta, 0, 2, 0);
- DataHelper.toLong(_meta, 2, 4, (_context.clock().now() + 500) / 1000);
- _context.random().nextBytes(_meta, 6, 6);
- Adler32 crc = new Adler32();
- crc.update(_meta, 0, _meta.length-4);
- DataHelper.toLong(_meta, _meta.length-4, 4, crc.getValue());
- _context.aes().encrypt(_meta, 0, encrypted, 0, _sessionKey, _prevWriteEnd, 0, _meta.length);
- }
- System.arraycopy(encrypted, encrypted.length-16, _prevWriteEnd, 0, _prevWriteEnd.length);
+ byte[] data = new byte[META_SIZE];
+ DataHelper.toLong(data, 0, 2, 0);
+ DataHelper.toLong(data, 2, 4, (_context.clock().now() + 500) / 1000);
+ _context.random().nextBytes(data, 6, 6);
+ Adler32 crc = new Adler32();
+ crc.update(data, 0, META_SIZE - 4);
+ DataHelper.toLong(data, META_SIZE - 4, 4, crc.getValue());
+ _context.aes().encrypt(data, 0, data, 0, _sessionKey, _prevWriteEnd, 0, META_SIZE);
+ System.arraycopy(data, META_SIZE - 16, _prevWriteEnd, 0, _prevWriteEnd.length);
// perhaps this should skip the bw limiter to reduce clock skew issues?
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending NTCP metadata");
_sendingMeta = true;
- _transport.getPumper().wantsWrite(this, encrypted);
+ _transport.getPumper().wantsWrite(this, data);
}
private static final int MAX_HANDLERS = 4;
@@ -1098,6 +1383,11 @@ public class NTCPConnection implements Closeable {
_i2npHandlers.clear();
}
+ private interface ReadState {
+ public void receive(ByteBuffer buf);
+ public void destroy();
+ }
+
/**
* Read the unencrypted message (16 bytes at a time).
* verify the checksum, and pass it on to
@@ -1115,95 +1405,250 @@ public class NTCPConnection implements Closeable {
* the ReadState._data and ._bais when _size is > 0, so there are only
* J 16KB buffers for the cons actually transmitting, instead of one per
* con (including idle ones)
+ *
+ * Call all methods from synchronized parent method.
+ *
*/
- private class ReadState {
+ private class NTCP1ReadState implements ReadState {
private int _size;
private ByteArray _dataBuf;
private int _nextWrite;
- private long _expectedCrc;
private final Adler32 _crc;
private long _stateBegin;
private int _blocks;
+ /** encrypted block of the current I2NP message being read */
+ private byte _curReadBlock[];
+ /** next byte to which data should be placed in the _curReadBlock */
+ private int _curReadBlockIndex;
+ private final byte _decryptBlockBuf[];
+ /** last AES block of the encrypted I2NP message (to serve as the next block's IV) */
+ private byte _prevReadBlock[];
- public ReadState() {
+ /**
+ * @param prevReadBlock 16 bytes AES IV
+ */
+ public NTCP1ReadState(byte[] prevReadBlock) {
_crc = new Adler32();
+ _prevReadBlock = prevReadBlock;
+ _curReadBlock = new byte[BLOCK_SIZE];
+ _decryptBlockBuf = new byte[BLOCK_SIZE];
init();
}
private void init() {
_size = -1;
_nextWrite = 0;
- _expectedCrc = -1;
_stateBegin = -1;
_blocks = -1;
_crc.reset();
if (_dataBuf != null)
releaseReadBuf(_dataBuf);
_dataBuf = null;
+ _curReadBlockIndex = 0;
}
- public int getSize() { return _size; }
-
+ /** @since 0.9.36 */
+ public void destroy() {
+ if (_dataBuf != null) {
+ releaseReadBuf(_dataBuf);
+ _dataBuf = null;
+ }
+ // TODO zero things out
+ }
+
/**
- * Caller must synchronize
- * @param buf 16 bytes
+ * Connection must be established!
+ *
+ * The contents of the buffer include some fraction of one or more
+ * encrypted and encoded I2NP messages. individual i2np messages are
+ * encoded as "sizeof(data)+data+pad+crc", and those are encrypted
+ * with the session key and the last 16 bytes of the previous encrypted
+ * i2np message.
+ *
+ * The NTCP connection now owns the buffer
+ * BUT it must copy out the data
+ * as reader will call EventPumper.releaseBuf().
+ *
+ * @since 0.9.36 moved from parent class
*/
- public void receiveBlock(byte buf[]) {
- if (_size == -1) {
- receiveInitial(buf);
- } else {
- receiveSubsequent(buf);
+ public void receive(ByteBuffer buf) {
+ // hasArray() is false for direct buffers, at least on my system...
+ if (_curReadBlockIndex == 0 && buf.hasArray()) {
+ // fast way
+ int tot = buf.remaining();
+ if (tot >= 32 && tot % 16 == 0) {
+ recvEncryptedFast(buf);
+ return;
+ }
+ }
+
+ while (buf.hasRemaining() && !_closed.get()) {
+ int want = Math.min(buf.remaining(), BLOCK_SIZE - _curReadBlockIndex);
+ if (want > 0) {
+ buf.get(_curReadBlock, _curReadBlockIndex, want);
+ _curReadBlockIndex += want;
+ }
+ if (_curReadBlockIndex >= BLOCK_SIZE) {
+ // cbc
+ _context.aes().decryptBlock(_curReadBlock, 0, _sessionKey, _decryptBlockBuf, 0);
+ xor16(_prevReadBlock, _decryptBlockBuf);
+ boolean ok = recvUnencryptedI2NP();
+ if (!ok) {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data, IV was: " + Base64.encode(_decryptBlockBuf));
+ _context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
+ return;
+ }
+ byte swap[] = _prevReadBlock;
+ _prevReadBlock = _curReadBlock;
+ _curReadBlock = swap;
+ _curReadBlockIndex = 0;
+ }
}
}
- /** @param buf 16 bytes */
- private void receiveInitial(byte buf[]) {
- _size = (int)DataHelper.fromLong(buf, 0, 2);
+ /**
+ * Decrypt directly out of the ByteBuffer instead of copying the bytes
+ * 16 at a time to the _curReadBlock / _prevReadBlock flip buffers.
+ *
+ * More efficient but can only be used if buf.hasArray == true AND
+ * _curReadBlockIndex must be 0 and buf.getRemaining() % 16 must be 0
+ * and buf.getRemaining() must be >= 16.
+ * All this is true for most incoming buffers.
+ * In theory this could be fixed up to handle the other cases too but that's hard.
+ * Caller must synchronize!
+ *
+ * @since 0.8.12, moved from parent class in 0.9.36
+ */
+ private void recvEncryptedFast(ByteBuffer buf) {
+ byte[] array = buf.array();
+ int pos = buf.arrayOffset() + buf.position();
+ int end = pos + buf.remaining();
+
+ // Copy to _curReadBlock for next IV...
+ System.arraycopy(array, end - BLOCK_SIZE, _curReadBlock, 0, BLOCK_SIZE);
+ // call aes().decrypt() to decrypt all at once, in place
+ // decrypt() will offload to the JVM/OS for larger sizes
+ _context.aes().decrypt(array, pos, array, pos, _sessionKey, _prevReadBlock, buf.remaining());
+
+ for ( ; pos < end; pos += BLOCK_SIZE) {
+ boolean ok = receiveBlock(array, pos);
+ if (!ok) {
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data");
+ _context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
+ return;
+ }
+ }
+ // ...and flip to _prevReadBlock for next time
+ byte swap[] = _prevReadBlock;
+ _prevReadBlock = _curReadBlock;
+ _curReadBlock = swap;
+ }
+
+ /**
+ * Append the next 16 bytes of cleartext to the read state.
+ * _decryptBlockBuf contains another cleartext block of I2NP to parse.
+ * Caller must synchronize!
+ *
+ * @return success
+ * @since 0.9.36 moved from parent class
+ */
+ private boolean recvUnencryptedI2NP() {
+ return receiveBlock(_decryptBlockBuf, 0);
+ }
+
+ /**
+ * Caller must synchronize
+ * @param buf 16 bytes starting at off
+ * @param off offset
+ * @return success, only false on initial block with invalid size
+ */
+ private boolean receiveBlock(byte buf[], int off) {
+ if (_size == -1) {
+ return receiveInitial(buf, off);
+ } else {
+ receiveSubsequent(buf, off);
+ return true;
+ }
+ }
+
+ /**
+ * Caller must synchronize
+ *
+ * @param buf 16 bytes starting at off
+ * @param off offset
+ * @return success
+ */
+ private boolean receiveInitial(byte buf[], int off) {
+ _size = (int)DataHelper.fromLong(buf, off, 2);
+ if (_size > BUFFER_SIZE) {
+ // this is typically an AES decryption error, not actually a large I2NP message
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("I2NP message too big - size: " + _size + " Closing " + NTCPConnection.this.toString(), new Exception());
+ _context.statManager().addRateData("ntcp.corruptTooLargeI2NP", _size);
+ close();
+ return false;
+ }
if (_size == 0) {
- readMeta(buf);
+ readMeta(buf, off);
init();
} else {
_stateBegin = _context.clock().now();
_dataBuf = acquireReadBuf();
- System.arraycopy(buf, 2, _dataBuf.getData(), 0, buf.length-2);
- _nextWrite += buf.length-2;
- _crc.update(buf);
+ System.arraycopy(buf, off + 2, _dataBuf.getData(), 0, BLOCK_SIZE - 2);
+ _nextWrite += BLOCK_SIZE - 2;
+ _crc.update(buf, off, BLOCK_SIZE);
_blocks++;
if (_log.shouldLog(Log.DEBUG))
_log.debug("new I2NP message with size: " + _size + " for message " + _messagesRead);
}
+ return true;
}
- /** @param buf 16 bytes */
- private void receiveSubsequent(byte buf[]) {
+ /**
+ * Caller must synchronize
+ *
+ * @param buf 16 bytes starting at off
+ * @param off offset
+ */
+ private void receiveSubsequent(byte buf[], int off) {
_blocks++;
int remaining = _size - _nextWrite;
- int blockUsed = Math.min(buf.length, remaining);
+ int blockUsed = Math.min(BLOCK_SIZE, remaining);
if (remaining > 0) {
- System.arraycopy(buf, 0, _dataBuf.getData(), _nextWrite, blockUsed);
+ System.arraycopy(buf, off, _dataBuf.getData(), _nextWrite, blockUsed);
_nextWrite += blockUsed;
remaining -= blockUsed;
}
- if ( (remaining <= 0) && (buf.length-blockUsed < 4) ) {
+ if ( (remaining <= 0) && (BLOCK_SIZE - blockUsed < 4) ) {
// we've received all the data but not the 4-byte checksum
if (_log.shouldLog(Log.DEBUG))
_log.debug("crc wraparound required on block " + _blocks + " in message " + _messagesRead);
- _crc.update(buf);
+ _crc.update(buf, off, BLOCK_SIZE);
return;
} else if (remaining <= 0) {
- receiveLastBlock(buf);
+ receiveLastBlock(buf, off);
} else {
- _crc.update(buf);
+ _crc.update(buf, off, BLOCK_SIZE);
}
}
- /** @param buf 16 bytes */
- private void receiveLastBlock(byte buf[]) {
+ /**
+ * This checks the checksum in buf only.
+ * All previous data, including that in buf, must have been copied to _dataBuf.
+ * Note that the checksum does not cover the padding.
+ * Caller must synchronize.
+ *
+ * @param buf 16 bytes starting at off
+ * @param off offset of the 16-byte block (NOT of the checksum only)
+ */
+ private void receiveLastBlock(byte buf[], int off) {
// on the last block
- _expectedCrc = DataHelper.fromLong(buf, buf.length-4, 4);
- _crc.update(buf, 0, buf.length-4);
+ long expectedCrc = DataHelper.fromLong(buf, off + BLOCK_SIZE - 4, 4);
+ _crc.update(buf, off, BLOCK_SIZE - 4);
long val = _crc.getValue();
- if (val == _expectedCrc) {
+ if (val == expectedCrc) {
try {
I2NPMessageHandler h = acquireHandler(_context);
@@ -1232,16 +1677,16 @@ public class NTCPConnection implements Closeable {
}
} catch (I2NPMessageException ime) {
if (_log.shouldLog(Log.WARN)) {
- _log.warn("Error parsing I2NP message" +
- "\nDUMP:\n" + HexDump.dump(_dataBuf.getData(), 0, _size) +
- "\nRAW:\n" + Base64.encode(_dataBuf.getData(), 0, _size) +
+ _log.warn("Error parsing I2NP message on " + NTCPConnection.this +
+ "\nDUMP:\n" + HexDump.dump(_dataBuf.getData(), 0, _size),
ime);
}
_context.statManager().addRateData("ntcp.corruptI2NPIME", 1);
}
} else {
if (_log.shouldLog(Log.WARN))
- _log.warn("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + _expectedCrc + ") size=" + _size + " blocks " + _blocks);
+ _log.warn("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + expectedCrc +
+ ") size=" + _size + " blocks=" + _blocks + " on: " + NTCPConnection.this);
_context.statManager().addRateData("ntcp.corruptI2NPCRC", 1);
}
// get it ready for the next I2NP message
@@ -1249,17 +1694,523 @@ public class NTCPConnection implements Closeable {
}
}
+ //// NTCP2 below here
+
+ /**
+ * We are Alice. NTCP2 only.
+ *
+ * Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
+ *
+ * @param clockSkew OUR clock minus BOB's clock in seconds (may be negative, obviously, but |val| should
+ * be under 1 minute)
+ * @param sender use to send to Bob
+ * @param receiver use to receive from Bob
+ * @param sip_ab 24 bytes to init SipHash to Bob
+ * @param sip_ba 24 bytes to init SipHash from Bob
+ * @since 0.9.36
+ */
+ synchronized void finishOutboundEstablishment(CipherState sender, CipherState receiver,
+ byte[] sip_ab, byte[] sip_ba, long clockSkew) {
+ finishEstablishment(sender, receiver, sip_ab, sip_ba, clockSkew);
+ _paddingConfig = OUR_PADDING;
+ _transport.markReachable(getRemotePeer().calculateHash(), false);
+ if (!_outbound.isEmpty())
+ _transport.getWriter().wantsWrite(this, "outbound established");
+ // NTCP2 outbound cannot have extra data
+ }
+
+ /**
+ * We are Bob. NTCP2 only.
+ *
+ * Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
+ *
+ * @param clockSkew OUR clock minus ALICE's clock in seconds (may be negative, obviously, but |val| should
+ * be under 1 minute)
+ * @param sender use to send to Alice
+ * @param receiver use to receive from Alice
+ * @param sip_ba 24 bytes to init SipHash to Alice
+ * @param sip_ab 24 bytes to init SipHash from Alice
+ * @param hisPadding may be null
+ * @since 0.9.36
+ */
+ synchronized void finishInboundEstablishment(CipherState sender, CipherState receiver,
+ byte[] sip_ba, byte[] sip_ab, long clockSkew,
+ NTCP2Options hisPadding) {
+ finishEstablishment(sender, receiver, sip_ba, sip_ab, clockSkew);
+ if (hisPadding != null) {
+ _paddingConfig = OUR_PADDING.merge(hisPadding);
+ if (_log.shouldWarn())
+ _log.warn("Got padding options:" +
+ "\nhis padding options: " + hisPadding +
+ "\nour padding options: " + OUR_PADDING +
+ "\nmerged config is: " + _paddingConfig);
+ } else {
+ _paddingConfig = OUR_PADDING;
+ }
+ NTCPConnection toClose = _transport.inboundEstablished(this);
+ if (toClose != null) {
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Old connection closed: " + toClose + " replaced by " + this);
+ _context.statManager().addRateData("ntcp.inboundEstablishedDuplicate", toClose.getUptime());
+ toClose.close();
+ }
+ enqueueInfoMessage();
+ }
+
+ /**
+ * We are Bob. NTCP2 only.
+ * This is only for invalid payload received in message 3. We send a termination and close.
+ * There will be no receiving.
+ *
+ * @param sender use to send to Alice
+ * @param sip_ba 24 bytes to init SipHash to Alice
+ * @since 0.9.36
+ */
+ synchronized void failInboundEstablishment(CipherState sender, byte[] sip_ba, int reason) {
+ _sender = sender;
+ _sendSipk1 = fromLong8LE(sip_ba, 0);
+ _sendSipk2 = fromLong8LE(sip_ba, 8);
+ _sendSipIV = new byte[SIP_IV_LENGTH];
+ System.arraycopy(sip_ba, 16, _sendSipIV, 0, SIP_IV_LENGTH);
+ if (_log.shouldWarn())
+ _log.warn("Send SipHash keys: " + _sendSipk1 + ' ' + _sendSipk2 + ' ' + Base64.encode(_sendSipIV));
+ _establishState = EstablishBase.VERIFIED;
+ _establishedOn = _context.clock().now();
+ _nextMetaTime = Long.MAX_VALUE;
+ _nextInfoTime = Long.MAX_VALUE;
+ sendTermination(reason, 0);
+ try { Thread.sleep(NTCP2_TERMINATION_CLOSE_DELAY); } catch (InterruptedException ie) {}
+ close();
+ }
+
+ /**
+ * We are Alice or Bob. NTCP2 only.
+ *
+ * @param clockSkew see above
+ * @param sender use to send
+ * @param receiver use to receive
+ * @param sip_send 24 bytes to init SipHash out
+ * @param sip_recv 24 bytes to init SipHash in
+ * @since 0.9.36
+ */
+ private synchronized void finishEstablishment(CipherState sender, CipherState receiver,
+ byte[] sip_send, byte[] sip_recv, long clockSkew) {
+ if (_establishState == EstablishBase.VERIFIED) {
+ IllegalStateException ise = new IllegalStateException("Already finished on " + this);
+ _log.error("Already finished", ise);
+ throw ise;
+ }
+ _sender = sender;
+ _sendSipk1 = fromLong8LE(sip_send, 0);
+ _sendSipk2 = fromLong8LE(sip_send, 8);
+ _sendSipIV = new byte[SIP_IV_LENGTH];
+ System.arraycopy(sip_send, 16, _sendSipIV, 0, SIP_IV_LENGTH);
+ if (_log.shouldWarn())
+ _log.warn("Send SipHash keys: " + _sendSipk1 + ' ' + _sendSipk2 + ' ' + Base64.encode(_sendSipIV));
+ _clockSkew = clockSkew;
+ _establishState = EstablishBase.VERIFIED;
+ _establishedOn = _context.clock().now();
+ _nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY);
+ _nextInfoTime = _establishedOn + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
+ _curReadState = new NTCP2ReadState(receiver, sip_recv);
+ }
+
+ /**
+ * Read the encrypted message
+ *
+ * Call all methods from synchronized parent method.
+ *
+ * @since 0.9.36
+ */
+ private class NTCP2ReadState implements ReadState, NTCP2Payload.PayloadCallback {
+ // temp to read the encrypted lengh into
+ private final byte[] _recvLen = new byte[2];
+ private final long _sipk1, _sipk2;
+ // the siphash ratchet, as a byte array
+ private final byte[] _sipIV = new byte[SIP_IV_LENGTH];
+ private final CipherState _rcvr;
+ // the size of the next frame, only valid if _received >= 0
+ private int _framelen;
+ // bytes received, -2 to _framelen
+ private int _received = -2;
+ private ByteArray _dataBuf;
+ // Valid frames received in data phase
+ private int _frameCount;
+ // for logging only
+ private int _blockCount;
+ private boolean _terminated;
+
+ /**
+ * @param keyData using first 24 bytes
+ */
+ public NTCP2ReadState(CipherState rcvr, byte[] keyData) {
+ _rcvr = rcvr;
+ _sipk1 = fromLong8LE(keyData, 0);
+ _sipk2 = fromLong8LE(keyData, 8);
+ System.arraycopy(keyData, 16, _sipIV, 0, SIP_IV_LENGTH);
+ if (_log.shouldWarn())
+ _log.warn("Recv SipHash keys: " + _sipk1 + ' ' + _sipk2 + ' ' + Base64.encode(_sipIV));
+ }
+
+ public void receive(ByteBuffer buf) {
+ if (_terminated)
+ return;
+ while (buf.hasRemaining()) {
+ if (_received == -2) {
+ _recvLen[0] = buf.get();
+ _received++;
+ }
+ if (_received == -1 && buf.hasRemaining()) {
+ _recvLen[1] = buf.get();
+ _received++;
+ long sipIV = SipHashInline.hash24(_sipk1, _sipk2, _sipIV);
+ //if (_log.shouldDebug())
+ // _log.debug("Got Encrypted frame length: " + DataHelper.fromLong(_recvLen, 0, 2) +
+ // " byte 1: " + (_recvLen[0] & 0xff) + " byte 2: " + (_recvLen[1] & 0xff) +
+ // " decrypting with keys " + _sipk1 + ' ' + _sipk2 + ' ' + Base64.encode(_sipIV) + ' ' + sipIV);
+ _recvLen[0] ^= (byte) (sipIV >> 8);
+ _recvLen[1] ^= (byte) sipIV;
+ toLong8LE(_sipIV, 0, sipIV);
+ _framelen = (int) DataHelper.fromLong(_recvLen, 0, 2);
+ if (_framelen < OutboundNTCP2State.MAC_SIZE) {
+ if (_log.shouldWarn())
+ _log.warn("Short frame length: " + _framelen);
+ // set a random length, then close
+ delayedClose(buf, _frameCount);
+ return;
+ }
+ //if (_log.shouldDebug())
+ // _log.debug("Next frame length: " + _framelen);
+ }
+ int remaining = buf.remaining();
+ if (remaining <= 0)
+ return;
+ if (_received == 0 && remaining >= _framelen) {
+ // shortcut, zero copy, decrypt directly to the ByteBuffer,
+ // overwriting the encrypted data
+ byte[] data = buf.array();
+ int pos = buf.position();
+ boolean ok = decryptAndProcess(data, pos);
+ buf.position(pos + _framelen);
+ if (!ok) {
+ delayedClose(buf, _frameCount);
+ return;
+ }
+ continue;
+ }
+
+ // allocate ByteArray,
+ // unless we have one already and it's big enough
+ if (_received == 0 && (_dataBuf == null || _dataBuf.getData().length < _framelen)) {
+ if (_dataBuf != null && _dataBuf.getData().length == BUFFER_SIZE)
+ releaseReadBuf(_dataBuf);
+ if (_framelen > BUFFER_SIZE) {
+ if (_log.shouldWarn())
+ _log.warn("Allocating big ByteArray: " + _framelen);
+ byte[] data = new byte[_framelen];
+ _dataBuf = new ByteArray(data);
+ } else {
+ _dataBuf = acquireReadBuf();
+ }
+ }
+
+ // We now have a ByteArray in _dataBuf,
+ // copy from ByteBuffer to ByteArray
+ int toGet = Math.min(buf.remaining(), _framelen - _received);
+ byte[] data = _dataBuf.getData();
+ buf.get(data, _received, toGet);
+ _received += toGet;
+ if (_received < _framelen)
+ return;
+ // decrypt to the ByteArray, overwriting the encrypted data
+ boolean ok = decryptAndProcess(data, 0);
+ // release buf only if we're not going around again
+ if (!ok || buf.remaining() < 2) {
+ if (!ok)
+ delayedClose(buf, _frameCount);
+ // delayedClose() may have zeroed out _databuf
+ if (_dataBuf != null) {
+ if (_dataBuf.getData().length == BUFFER_SIZE)
+ releaseReadBuf(_dataBuf);
+ _dataBuf = null;
+ }
+ if (!ok)
+ return;
+ }
+ // go around again
+ }
+ }
+
+ /**
+ * Decrypts in place.
+ * Length is _framelen
+ * Side effects: Sets _received = -2, increments _frameCount and _blockCount if valid
+ *
+ * Does not call close() on failure. Caller MUST call delayedClose() if this returns false.
+ *
+ * @return success, false for fatal error (AEAD) only
+ */
+ private boolean decryptAndProcess(byte[] data, int off) {
+ if (_log.shouldWarn())
+ _log.warn("Decrypting frame " + _frameCount + " with " + _framelen + " bytes");
+ try {
+ _rcvr.decryptWithAd(null, data, off, data, off, _framelen);
+ } catch (GeneralSecurityException gse) {
+ // TODO set a random length, then close
+ if (_log.shouldWarn())
+ _log.warn("Bad AEAD data phase frame " + _frameCount + " on " + NTCPConnection.this, gse);
+ return false;
+ }
+ try {
+ int blocks = NTCP2Payload.processPayload(_context, this, data, off,
+ _framelen - OutboundNTCP2State.MAC_SIZE, false);
+ if (_log.shouldWarn())
+ _log.warn("Processed " + blocks + " blocks in frame");
+ _blockCount += blocks;
+ } catch (IOException ioe) {
+ if (_log.shouldWarn())
+ _log.warn("Fail payload " + NTCPConnection.this, ioe);
+ } catch (DataFormatException dfe) {
+ if (_log.shouldWarn())
+ _log.warn("Fail payload " + NTCPConnection.this, dfe);
+ } catch (I2NPMessageException ime) {
+ if (_log.shouldWarn())
+ _log.warn("Error parsing I2NP message on " + NTCPConnection.this, ime);
+ _context.statManager().addRateData("ntcp.corruptI2NPIME", 1);
+ }
+ _received = -2;
+ _frameCount++;
+ return !_terminated;
+ }
+
+ public void destroy() {
+ if (_dataBuf != null && _dataBuf.getData().length == BUFFER_SIZE)
+ releaseReadBuf(_dataBuf);
+ _dataBuf = null;
+ _rcvr.destroy();
+ _terminated = true;
+ }
+
+ //// PayloadCallbacks
+
+ public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
+ if (_log.shouldWarn())
+ _log.warn("Got updated RI");
+ _messagesRead.incrementAndGet();
+ try {
+ Hash h = ri.getHash();
+ RouterInfo old = _context.netDb().store(h, ri);
+ if (flood && !ri.equals(old)) {
+ FloodfillNetworkDatabaseFacade fndf = (FloodfillNetworkDatabaseFacade) _context.netDb();
+ if (fndf.floodConditional(ri)) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Flooded the RI: " + h);
+ } else {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Flood request but we didn't: " + h);
+ }
+ }
+ } catch (IllegalArgumentException iae) {
+ throw new DataFormatException("RI store fail", iae);
+ }
+ }
+
+ public void gotDateTime(long time) {
+ if (_log.shouldWarn())
+ _log.warn("Got updated datetime block");
+ receiveTimestamp((time + 500) / 1000);
+ // update skew
+ }
+
+ public void gotI2NP(I2NPMessage msg) {
+ if (_log.shouldWarn())
+ _log.warn("Got I2NP msg: " + msg);
+ long timeToRecv = 0; // _context.clock().now() - _stateBegin;
+ int size = 100; // FIXME
+ _transport.messageReceived(msg, _remotePeer, null, timeToRecv, size);
+ _lastReceiveTime = _context.clock().now();
+ _messagesRead.incrementAndGet();
+ // TEST send back. null RI for target, not necesary
+ //if (_context.getBooleanProperty("i2np.ntcp2.loopback"))
+ // send(new OutNetMessage(_context, msg, _context.clock().now() + 10*1000, OutNetMessage.PRIORITY_MY_DATA, null));
+ }
+
+ public void gotOptions(byte[] options, boolean isHandshake) {
+ if (options.length < 12) {
+ if (_log.shouldWarn())
+ _log.warn("Got options length " + options.length + " on: " + this);
+ return;
+ }
+ float tmin = (options[0] & 0xff) / 16.0f;
+ float tmax = (options[1] & 0xff) / 16.0f;
+ float rmin = (options[2] & 0xff) / 16.0f;
+ float rmax = (options[3] & 0xff) / 16.0f;
+ int tdummy = (int) DataHelper.fromLong(options, 4, 2);
+ int rdummy = (int) DataHelper.fromLong(options, 6, 2);
+ int tdelay = (int) DataHelper.fromLong(options, 8, 2);
+ int rdelay = (int) DataHelper.fromLong(options, 10, 2);
+ NTCP2Options hisPadding = new NTCP2Options(tmin, tmax, rmin, rmax,
+ tdummy, rdummy, tdelay, rdelay);
+ _paddingConfig = OUR_PADDING.merge(hisPadding);
+ if (_log.shouldWarn())
+ _log.warn("Got padding options:" +
+ "\nhis padding options: " + hisPadding +
+ "\nour padding options: " + OUR_PADDING +
+ "\nmerged config is: " + _paddingConfig);
+ }
+
+ public void gotTermination(int reason, long lastReceived) {
+ if (_log.shouldWarn())
+ _log.warn("Got Termination: " + reason + " total rcvd: " + lastReceived);
+ _terminated = true;
+ close();
+ }
+
+ public void gotUnknown(int type, int len) {
+ if (_log.shouldWarn())
+ _log.warn("Got unknown block type " + type + " length " + len);
+ }
+
+ public void gotPadding(int paddingLength, int frameLength) {
+ if (_log.shouldWarn())
+ _log.warn("Got " + paddingLength +
+ " bytes padding in " + frameLength +
+ " byte frame; ratio: " + (((float) paddingLength) / ((float) frameLength)) +
+ " configured min: " + _paddingConfig.getRecvMin() +
+ " configured max: " + _paddingConfig.getRecvMax());
+ }
+ }
+
+ /**
+ * After an AEAD failure, read a random number of bytes,
+ * with a brief timeout, and then fail.
+ * This replaces _curReadState, so no more messages will be received.
+ *
+ * @param buf possibly with data remaining
+ * @param validFramesRcvd to be sent in termination message
+ * @since 0.9.36
+ */
+ private void delayedClose(ByteBuffer buf, int validFramesRcvd) {
+ int toRead = 18 + _context.random().nextInt(NTCP2_FAIL_READ);
+ int remaining = toRead - buf.remaining();
+ if (remaining > 0) {
+ if (_log.shouldWarn())
+ _log.warn("delayed close after AEAD failure, to read: " + toRead);
+ _curReadState = new NTCP2FailState(toRead, validFramesRcvd);
+ _curReadState.receive(buf);
+ } else {
+ if (_log.shouldWarn())
+ _log.warn("immediate close after AEAD failure and reading " + toRead);
+ sendTermination(REASON_AEAD, validFramesRcvd);
+ try { Thread.sleep(NTCP2_TERMINATION_CLOSE_DELAY); } catch (InterruptedException ie) {}
+ close();
+ }
+ }
+
+ /**
+ * After an AEAD failure, read a random number of bytes,
+ * with a brief timeout, and then fail.
+ *
+ * Call all methods from synchronized parent method.
+ *
+ * @since 0.9.36
+ */
+ private class NTCP2FailState extends SimpleTimer2.TimedEvent implements ReadState {
+ private final int _toRead;
+ private final int _validFramesRcvd;
+ private int _read;
+
+ /**
+ * @param toRead how much left to read
+ * @param validFramesRcvd to be sent in termination message
+ */
+ public NTCP2FailState(int toRead, int validFramesRcvd) {
+ super(_context.simpleTimer2());
+ _toRead = toRead;
+ _validFramesRcvd = validFramesRcvd;
+ schedule(NTCP2_FAIL_TIMEOUT);
+ }
+
+ public void receive(ByteBuffer buf) {
+ _read += buf.remaining();
+ if (_read >= _toRead) {
+ cancel();
+ if (_log.shouldWarn())
+ _log.warn("close after AEAD failure and reading " + _toRead);
+ sendTermination(REASON_AEAD, _validFramesRcvd);
+ try { Thread.sleep(NTCP2_TERMINATION_CLOSE_DELAY); } catch (InterruptedException ie) {}
+ close();
+ }
+ }
+
+ public void destroy() {
+ cancel();
+ }
+
+ public void timeReached() {
+ if (_log.shouldWarn())
+ _log.warn("timeout after AEAD failure waiting for more data");
+ sendTermination(REASON_AEAD, _validFramesRcvd);
+ try { Thread.sleep(NTCP2_TERMINATION_CLOSE_DELAY); } catch (InterruptedException ie) {}
+ close();
+ }
+ }
+
+ //// Utils
+
+ /**
+ * XOR a into b. Modifies b. a is unmodified.
+ * @param a 16 bytes
+ * @param b 16 bytes
+ * @since 0.9.36
+ */
+ private static void xor16(byte[] a, byte[] b) {
+ for (int i = 0; i < BLOCK_SIZE; i++) {
+ b[i] ^= a[i];
+ }
+ }
+
+ /**
+ * Little endian.
+ * Same as DataHelper.fromlongLE(src, offset, 8) but allows negative result
+ *
+ * @throws ArrayIndexOutOfBoundsException
+ * @since 0.9.36
+ */
+ private static long fromLong8LE(byte src[], int offset) {
+ long rv = 0;
+ for (int i = offset + 7; i >= offset; i--) {
+ rv <<= 8;
+ rv |= src[i] & 0xFF;
+ }
+ return rv;
+ }
+
+ /**
+ * Little endian.
+ * Same as DataHelper.fromlongLE(target, offset, 8, value) but allows negative value
+ *
+ */
+ private static void toLong8LE(byte target[], int offset, long value) {
+ int limit = offset + 8;
+ for (int i = offset; i < limit; i++) {
+ target[i] = (byte) value;
+ value >>= 8;
+ }
+ }
+
@Override
public String toString() {
- return "NTCP conn " +
+ return "NTCP" + _version + " conn " +
_connID +
- (_isInbound ? " from " : " to ") +
+ (_isInbound ? (" from " + _chan.socket().getInetAddress() + " port " + _chan.socket().getPort() + ' ')
+ : (" to " + _remAddr.getHost() + " port " + _remAddr.getPort() + ' ')) +
(_remotePeer == null ? "unknown" : _remotePeer.calculateHash().toBase64().substring(0,6)) +
(isEstablished() ? "" : " not established") +
" created " + DataHelper.formatDuration(getTimeSinceCreated()) + " ago," +
" last send " + DataHelper.formatDuration(getTimeSinceSend()) + " ago," +
" last recv " + DataHelper.formatDuration(getTimeSinceReceive()) + " ago," +
- " sent " + _messagesWritten + "," +
+ " sent " + _messagesWritten + ',' +
" rcvd " + _messagesRead;
}
}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index 2f5b21d69..e106e64be 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -8,6 +8,7 @@ import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
+import java.security.KeyPair;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -45,6 +46,9 @@ import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportUtil;
import static net.i2p.router.transport.TransportUtil.IPv6Config.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
+import net.i2p.router.transport.crypto.X25519KeyFactory;
+import net.i2p.router.transport.crypto.X25519PublicKey;
+import net.i2p.router.transport.crypto.X25519PrivateKey;
import net.i2p.router.util.DecayingHashSet;
import net.i2p.router.util.DecayingBloomFilter;
import net.i2p.util.Addresses;
@@ -97,13 +101,15 @@ public class NTCPTransport extends TransportImpl {
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip";
private static final String PROP_ADVANCED = "routerconsole.advanced";
- public static final int DEFAULT_COST = 10;
+ private static final int DEFAULT_COST = 10;
+ private static final int NTCP2_OUTBOUND_COST = 14;
/** this is rarely if ever used, default is to bind to wildcard address */
public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface";
private final NTCPSendFinisher _finisher;
private final DHSessionKeyBuilder.Factory _dhFactory;
+ private final X25519KeyFactory _xdhFactory;
private long _lastBadSkew;
private static final long[] RATES = { 10*60*1000 };
@@ -114,28 +120,32 @@ public class NTCPTransport extends TransportImpl {
// NTCP2 stuff
public static final String STYLE = "NTCP";
- private static final String STYLE2 = "NTCP2";
- private static final String PROP_NTCP2_ENABLE = "i2np.ntcp2.enable";
- private static final boolean DEFAULT_NTCP2_ENABLE = false;
- private boolean _enableNTCP2;
- private static final String NTCP2_PROTO_SHORT = "NXK2CS";
- private static final String OPT_NTCP2_SK = 'N' + NTCP2_PROTO_SHORT + "2s";
+ public static final String STYLE2 = "NTCP2";
static final int NTCP2_INT_VERSION = 2;
- private static final String NTCP2_VERSION = Integer.toString(NTCP2_INT_VERSION);
+ /** "2" */
+ static final String NTCP2_VERSION = Integer.toString(NTCP2_INT_VERSION);
+ /** "2," */
+ static final String NTCP2_VERSION_ALT = NTCP2_VERSION + ',';
/** b64 static private key */
- private static final String PROP_NTCP2_SP = "i2np.ntcp2.sp";
+ public static final String PROP_NTCP2_SP = "i2np.ntcp2.sp";
/** b64 static IV */
- private static final String PROP_NTCP2_IV = "i2np.ntcp2.iv";
- private static final int NTCP2_IV_LEN = 16;
- private static final int NTCP2_KEY_LEN = 32;
+ public static final String PROP_NTCP2_IV = "i2np.ntcp2.iv";
+ private static final int NTCP2_IV_LEN = OutboundNTCP2State.IV_SIZE;
+ private static final int NTCP2_KEY_LEN = OutboundNTCP2State.KEY_SIZE;
+ private final boolean _enableNTCP2;
+ private final byte[] _ntcp2StaticPubkey;
private final byte[] _ntcp2StaticPrivkey;
private final byte[] _ntcp2StaticIV;
private final String _b64Ntcp2StaticPubkey;
private final String _b64Ntcp2StaticIV;
- public NTCPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) {
+ /**
+ * @param xdh null to disable NTCP2
+ */
+ public NTCPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh, X25519KeyFactory xdh) {
super(ctx);
_dhFactory = dh;
+ _xdhFactory = xdh;
_log = ctx.logManager().getLog(getClass());
_context.statManager().createRateStat("ntcp.sendTime", "Total message lifetime when sent completely", "ntcp", RATES);
@@ -222,27 +232,31 @@ public class NTCPTransport extends TransportImpl {
_nearCapacityCostBid = new SharedBid(105);
_transientFail = new SharedBid(TransportBid.TRANSIENT_FAIL);
- //_enableNTCP2 = ctx.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE);
- _enableNTCP2 = false;
+ _enableNTCP2 = xdh != null;
if (_enableNTCP2) {
boolean shouldSave = false;
byte[] priv = null;
byte[] iv = null;
- String b64Pub = null;
String b64IV = null;
String s = ctx.getProperty(PROP_NTCP2_SP);
if (s != null) {
priv = Base64.decode(s);
}
if (priv == null || priv.length != NTCP2_KEY_LEN) {
- priv = new byte[NTCP2_KEY_LEN];
- ctx.random().nextBytes(priv);
+ KeyPair keys = xdh.getKeys();
+ _ntcp2StaticPrivkey = keys.getPrivate().getEncoded();
+ _ntcp2StaticPubkey = keys.getPublic().getEncoded();
shouldSave = true;
+ } else {
+ _ntcp2StaticPrivkey = priv;
+ _ntcp2StaticPubkey = (new X25519PrivateKey(priv)).toPublic().getEncoded();
}
- s = ctx.getProperty(PROP_NTCP2_IV);
- if (s != null) {
- iv = Base64.decode(s);
- b64IV = s;
+ if (!shouldSave) {
+ s = ctx.getProperty(PROP_NTCP2_IV);
+ if (s != null) {
+ iv = Base64.decode(s);
+ b64IV = s;
+ }
}
if (iv == null || iv.length != NTCP2_IV_LEN) {
iv = new byte[NTCP2_IV_LEN];
@@ -251,17 +265,17 @@ public class NTCPTransport extends TransportImpl {
}
if (shouldSave) {
Map changes = new HashMap(2);
- String b64Priv = Base64.encode(priv);
+ String b64Priv = Base64.encode(_ntcp2StaticPrivkey);
b64IV = Base64.encode(iv);
changes.put(PROP_NTCP2_SP, b64Priv);
changes.put(PROP_NTCP2_IV, b64IV);
ctx.router().saveConfig(changes, null);
}
- _ntcp2StaticPrivkey = priv;
_ntcp2StaticIV = iv;
- _b64Ntcp2StaticPubkey = "TODO"; // priv->pub
+ _b64Ntcp2StaticPubkey = Base64.encode(_ntcp2StaticPubkey);
_b64Ntcp2StaticIV = b64IV;
} else {
+ _ntcp2StaticPubkey = null;
_ntcp2StaticPrivkey = null;
_ntcp2StaticIV = null;
_b64Ntcp2StaticPubkey = null;
@@ -299,17 +313,17 @@ public class NTCPTransport extends TransportImpl {
RouterIdentity ident = target.getIdentity();
Hash ih = ident.calculateHash();
NTCPConnection con = null;
- boolean isNew = false;
+ int newVersion = 0;
boolean fail = false;
synchronized (_conLock) {
con = _conByIdent.get(ih);
if (con == null) {
- isNew = true;
RouterAddress addr = getTargetAddress(target);
if (addr != null) {
- int ver = getNTCPVersion(addr);
- if (ver != 0) {
- con = new NTCPConnection(_context, this, ident, addr, ver);
+ newVersion = getNTCPVersion(addr);
+ if (newVersion != 0) {
+ con = new NTCPConnection(_context, this, ident, addr, newVersion);
+ establishing(con);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Send on a new con: " + con + " at " + addr + " for " + ih);
// Note that outbound conns go in the map BEFORE establishment
@@ -331,9 +345,7 @@ public class NTCPTransport extends TransportImpl {
afterSend(msg, false);
return;
}
- if (isNew) {
- // doesn't do anything yet, just enqueues it
- con.send(msg);
+ if (newVersion != 0) {
// As of 0.9.12, don't send our info if the first message is
// doing the same (common when connecting to a floodfill).
// Also, put the info message after whatever we are trying to send
@@ -341,16 +353,27 @@ public class NTCPTransport extends TransportImpl {
// Prior to 0.9.12, Bob would not send his RI unless he had ours,
// but that's fixed in 0.9.12.
boolean shouldSkipInfo = false;
+ boolean shouldFlood = false;
I2NPMessage m = msg.getMessage();
if (m.getType() == DatabaseStoreMessage.MESSAGE_TYPE) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage) m;
if (dsm.getKey().equals(_context.routerHash())) {
shouldSkipInfo = true;
+ shouldFlood = dsm.getReplyToken() != 0;
+ // TODO tell the NTCP2 con to flood in the handshake and mark success when sent
}
}
if (!shouldSkipInfo) {
+ // Queue the message, and our RI
+ // doesn't do anything yet, just enqueues it
+ con.send(msg);
con.enqueueInfoMessage();
+ } else if (shouldFlood || newVersion == 1) {
+ // Queue the message, which is a DSM of our RI
+ con.send(msg);
} else if (_log.shouldLog(Log.INFO)) {
+ // Send nothing, the handshake has the RI
+ // version == 2 && shouldSkipInfo && !shouldFlood
_log.info("SKIPPING INFO message: " + con);
}
@@ -365,6 +388,10 @@ public class NTCPTransport extends TransportImpl {
_log.error("Error opening a channel", ioe);
_context.statManager().addRateData("ntcp.outboundFailedIOEImmediate", 1);
con.close();
+ afterSend(msg, false);
+ } catch (IllegalStateException ise) {
+ _log.error("Failed opening a channel", ise);
+ afterSend(msg, false);
}
} else {
con.send(msg);
@@ -677,6 +704,7 @@ public class NTCPTransport extends TransportImpl {
long tooOld = _context.clock().now() - 10*60*1000;
for (NTCPConnection con : _conByIdent.values()) {
+ // TODO skip isEstablished() check?
if (con.isEstablished() && con.getCreated() > tooOld)
skews.addElement(Long.valueOf(con.getClockSkew()));
}
@@ -696,7 +724,7 @@ public class NTCPTransport extends TransportImpl {
* As there is no timestamp in the first message, we can't detect
* something long-delayed. To be fixed in next version of NTCP.
*
- * @param hxhi 32 bytes
+ * @param hxhi using first 8 bytes only
* @return valid
* @since 0.9.12
*/
@@ -740,19 +768,37 @@ public class NTCPTransport extends TransportImpl {
replaceAddress(addr);
} else if (port > 0) {
// all detected interfaces
- for (InetAddress ia : getSavedLocalAddresses()) {
- OrderedProperties props = new OrderedProperties();
- props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress());
- props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port));
- addNTCP2Options(props);
- int cost = getDefaultCost(ia instanceof Inet6Address);
- myAddress = new RouterAddress(STYLE, props, cost);
- replaceAddress(myAddress);
+ Collection addrs = getSavedLocalAddresses();
+ if (!addrs.isEmpty()) {
+ for (InetAddress ia : addrs) {
+ OrderedProperties props = new OrderedProperties();
+ props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress());
+ props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port));
+ addNTCP2Options(props);
+ int cost = getDefaultCost(ia instanceof Inet6Address);
+ myAddress = new RouterAddress(STYLE, props, cost);
+ replaceAddress(myAddress);
+ }
+ } else if (_enableNTCP2) {
+ setOutboundNTCP2Address();
}
+ } else if (_enableNTCP2) {
+ setOutboundNTCP2Address();
}
// TransportManager.startListening() calls router.rebuildRouterInfo()
}
+ /**
+ * Outbound only, NTCP2 with "s" and "v" only
+ * @since 0.9.36
+ */
+ private void setOutboundNTCP2Address() {
+ OrderedProperties props = new OrderedProperties();
+ addNTCP2Options(props);
+ RouterAddress myAddress = new RouterAddress(STYLE2, props, NTCP2_OUTBOUND_COST);
+ replaceAddress(myAddress);
+ }
+
/**
* Only called by externalAddressReceived().
* Calls replaceAddress() or removeAddress().
@@ -966,6 +1012,14 @@ public class NTCPTransport extends TransportImpl {
return _dhFactory.getBuilder();
}
+ /**
+ * @return null if not configured for NTCP2
+ * @since 0.9.36
+ */
+ X25519KeyFactory getXDHFactory() {
+ return _xdhFactory;
+ }
+
/**
* Return an unused DH key builder
* to be put back onto the queue for reuse.
@@ -1071,15 +1125,17 @@ public class NTCPTransport extends TransportImpl {
}
/**
- * Add the required options to the properties for a NTCP2 address
+ * Add the required options to the properties for a NTCP2 address.
+ * Host/port must already be set in props if they are going to be.
*
* @since 0.9.35
*/
private void addNTCP2Options(Properties props) {
if (!_enableNTCP2)
return;
- props.setProperty("i", _b64Ntcp2StaticIV);
- props.setProperty("n", NTCP2_PROTO_SHORT);
+ // only set i if we are not firewalled
+ if (props.containsKey("host"))
+ props.setProperty("i", _b64Ntcp2StaticIV);
props.setProperty("s", _b64Ntcp2StaticPubkey);
props.setProperty("v", NTCP2_VERSION);
}
@@ -1091,6 +1147,15 @@ public class NTCPTransport extends TransportImpl {
*/
boolean isNTCP2Enabled() { return _enableNTCP2; }
+ /**
+ * The static priv key
+ *
+ * @since 0.9.36
+ */
+ byte[] getNTCP2StaticPubkey() {
+ return _ntcp2StaticPubkey;
+ }
+
/**
* The static priv key
*
@@ -1101,7 +1166,17 @@ public class NTCPTransport extends TransportImpl {
}
/**
- * Get the valid NTCP version of this NTCP address.
+ * The static IV
+ *
+ * @since 0.9.36
+ */
+ byte[] getNTCP2StaticIV() {
+ return _ntcp2StaticIV;
+ }
+
+ /**
+ * Get the valid NTCP version of Bob's NTCP address
+ * for our outbound connections as Alice.
*
* @return the valid version 1 or 2, or 0 if unusable
* @since 0.9.35
@@ -1116,18 +1191,21 @@ public class NTCPTransport extends TransportImpl {
} else if (style.equals(STYLE2)) {
if (!_enableNTCP2)
return 0;
- rv = 2;
+ rv = NTCP2_INT_VERSION;
} else {
return 0;
}
- if (addr.getOption("s") == null ||
+ // check version == "2" || version starts with "2,"
+ // and static key, and iv
+ String v = addr.getOption("v");
+ if (v == null ||
addr.getOption("i") == null ||
- !NTCP2_VERSION.equals(addr.getOption("v")) ||
- !NTCP2_PROTO_SHORT.equals(addr.getOption("n"))) {
+ addr.getOption("s") == null ||
+ (!v.equals(NTCP2_VERSION) && !v.startsWith(NTCP2_VERSION_ALT))) {
return (rv == 1) ? 1 : 0;
}
// todo validate s/i b64, or just catch it later?
- return rv;
+ return NTCP2_INT_VERSION;
}
/**
@@ -1314,7 +1392,6 @@ public class NTCPTransport extends TransportImpl {
int cost;
if (oldAddr == null) {
cost = getDefaultCost(isIPv6);
- addNTCP2Options(newProps);
} else {
cost = oldAddr.getCost();
newProps.putAll(oldAddr.getOptionsMap());
@@ -1436,6 +1513,7 @@ public class NTCPTransport extends TransportImpl {
return;
}
}
+ addNTCP2Options(newProps);
// stopListening stops the pumper, readers, and writers, so required even if
// oldAddr == null since startListening starts them all again
diff --git a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java
index e7af82c42..c40573803 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java
@@ -3,6 +3,7 @@ package net.i2p.router.transport.ntcp;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.Arrays;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
@@ -33,13 +34,13 @@ class OutboundEstablishState extends EstablishBase {
}
/**
- * parse the contents of the buffer as part of the handshake. if the
- * handshake is completed and there is more data remaining, the data are
- * copieed out so that the next read will be the (still encrypted) remaining
- * data (available from getExtraBytes)
+ * Parse the contents of the buffer as part of the handshake.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
+ *
+ * If there are additional data in the buffer after the handshake is complete,
+ * the EstablishState is responsible for passing it to NTCPConnection.
*/
@Override
public synchronized void receive(ByteBuffer src) {
@@ -65,124 +66,129 @@ class OutboundEstablishState extends EstablishBase {
*
* Caller must synch.
*
- * FIXME none of the _state comparisons use _stateLock, but whole thing
- * is synchronized, should be OK. See isComplete()
*/
private void receiveOutbound(ByteBuffer src) {
// recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
// Read in Y, which is the first part of message #2
- while (_state == State.OB_SENT_X && src.hasRemaining()) {
- byte c = src.get();
- _Y[_received++] = c;
- if (_received >= XY_SIZE) {
- try {
- _dh.setPeerPublicValue(_Y);
- _dh.getSessionKey(); // force the calc
- if (_log.shouldLog(Log.DEBUG))
- _log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
- changeState(State.OB_GOT_Y);
- } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
- _context.statManager().addRateData("ntcp.invalidDH", 1);
- fail("Invalid X", e);
- return;
- }
+ if (_state == State.OB_SENT_X && src.hasRemaining()) {
+ int toGet = Math.min(src.remaining(), XY_SIZE - _received);
+ src.get(_Y, _received, toGet);
+ _received += toGet;
+ if (_received < XY_SIZE)
+ return;
+
+ try {
+ _dh.setPeerPublicValue(_Y);
+ _dh.getSessionKey(); // force the calc
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
+ changeState(State.OB_GOT_Y);
+ _received = 0;
+ } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
+ _context.statManager().addRateData("ntcp.invalidDH", 1);
+ fail("Invalid X", e);
+ return;
+ } catch (IllegalStateException ise) {
+ // setPeerPublicValue()
+ fail("reused keys?", ise);
+ return;
}
}
- // Read in Y, which is the first part of message #2
// Read in the rest of message #2
- while (_state == State.OB_GOT_Y && src.hasRemaining()) {
- int i = _received-XY_SIZE;
- _received++;
- byte c = src.get();
- _e_hXY_tsB[i] = c;
- if (i+1 >= HXY_TSB_PAD_SIZE) {
- if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
- byte hXY_tsB[] = new byte[HXY_TSB_PAD_SIZE];
- _context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
- byte XY[] = new byte[XY_SIZE + XY_SIZE];
- System.arraycopy(_X, 0, XY, 0, XY_SIZE);
- System.arraycopy(_Y, 0, XY, XY_SIZE, XY_SIZE);
- byte[] h = SimpleByteCache.acquire(HXY_SIZE);
- _context.sha().calculateHash(XY, 0, XY_SIZE + XY_SIZE, h, 0);
- if (!DataHelper.eq(h, 0, hXY_tsB, 0, HXY_SIZE)) {
- SimpleByteCache.release(h);
- _context.statManager().addRateData("ntcp.invalidHXY", 1);
- fail("Invalid H(X+Y) - mitm attack attempted?");
- return;
- }
+ if (_state == State.OB_GOT_Y && src.hasRemaining()) {
+ int toGet = Math.min(src.remaining(), HXY_TSB_PAD_SIZE - _received);
+ src.get(_e_hXY_tsB, _received, toGet);
+ _received += toGet;
+ if (_received < HXY_TSB_PAD_SIZE)
+ return;
+
+ if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
+ byte hXY_tsB[] = new byte[HXY_TSB_PAD_SIZE];
+ _context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
+ byte XY[] = new byte[XY_SIZE + XY_SIZE];
+ System.arraycopy(_X, 0, XY, 0, XY_SIZE);
+ System.arraycopy(_Y, 0, XY, XY_SIZE, XY_SIZE);
+ byte[] h = SimpleByteCache.acquire(HXY_SIZE);
+ _context.sha().calculateHash(XY, 0, XY_SIZE + XY_SIZE, h, 0);
+ if (!DataHelper.eq(h, 0, hXY_tsB, 0, HXY_SIZE)) {
SimpleByteCache.release(h);
- changeState(State.OB_GOT_HXY);
- // their (Bob's) timestamp in seconds
- _tsB = DataHelper.fromLong(hXY_tsB, HXY_SIZE, 4);
- long now = _context.clock().now();
- // rtt from sending #1 to receiving #2
- long rtt = now - _con.getCreated();
- // our (Alice's) timestamp in seconds
- _tsA = (now + 500) / 1000;
- _peerSkew = (now - (_tsB * 1000) - (rtt / 2) + 500) / 1000;
- if (_log.shouldLog(Log.DEBUG))
- _log.debug(prefix()+"h(X+Y) is correct, skew = " + _peerSkew);
-
- // the skew is not authenticated yet, but it is certainly fatal to
- // the establishment, so fail hard if appropriate
- long diff = 1000*Math.abs(_peerSkew);
- if (!_context.clock().getUpdatedSuccessfully()) {
- // Adjust the clock one time in desperation
- // We are Alice, he is Bob, adjust to match Bob
- _context.clock().setOffset(1000 * (0 - _peerSkew), true);
- _peerSkew = 0;
- if (diff != 0)
- _log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
- } else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
- _context.statManager().addRateData("ntcp.invalidOutboundSkew", diff);
- _transport.markReachable(_con.getRemotePeer().calculateHash(), false);
- // Only banlist if we know what time it is
- _context.banlist().banlistRouter(DataHelper.formatDuration(diff),
- _con.getRemotePeer().calculateHash(),
- _x("Excessive clock skew: {0}"));
- _transport.setLastBadSkew(_peerSkew);
- fail("Clocks too skewed (" + diff + " ms)", null, true);
- return;
- } else if (_log.shouldLog(Log.DEBUG)) {
- _log.debug(prefix()+"Clock skew: " + diff + " ms");
- }
-
- // now prepare and send our response
- // send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
- int sigSize = XY_SIZE + XY_SIZE + HXY_SIZE + 4+4;//+12;
- byte preSign[] = new byte[sigSize];
- System.arraycopy(_X, 0, preSign, 0, XY_SIZE);
- System.arraycopy(_Y, 0, preSign, XY_SIZE, XY_SIZE);
- System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, XY_SIZE + XY_SIZE, HXY_SIZE);
- DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE, 4, _tsA);
- DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE + 4, 4, _tsB);
- // hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
- Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
-
- byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
- // handle variable signature size
- int min = 2 + ident.length + 4 + sig.length();
- int rem = min % AES_SIZE;
- int padding = 0;
- if (rem > 0)
- padding = AES_SIZE - rem;
- byte preEncrypt[] = new byte[min+padding];
- DataHelper.toLong(preEncrypt, 0, 2, ident.length);
- System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
- DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
- if (padding > 0)
- _context.random().nextBytes(preEncrypt, 2 + ident.length + 4, padding);
- System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, sig.length());
-
- _prevEncrypted = new byte[preEncrypt.length];
- _context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(),
- _hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-AES_SIZE, preEncrypt.length);
-
- changeState(State.OB_SENT_RI);
- _transport.getPumper().wantsWrite(_con, _prevEncrypted);
+ _context.statManager().addRateData("ntcp.invalidHXY", 1);
+ fail("Invalid H(X+Y) - mitm attack attempted?");
+ return;
}
+ SimpleByteCache.release(h);
+ changeState(State.OB_GOT_HXY);
+ _received = 0;
+ // their (Bob's) timestamp in seconds
+ _tsB = DataHelper.fromLong(hXY_tsB, HXY_SIZE, 4);
+ long now = _context.clock().now();
+ // rtt from sending #1 to receiving #2
+ long rtt = now - _con.getCreated();
+ // our (Alice's) timestamp in seconds
+ _tsA = (now + 500) / 1000;
+ _peerSkew = (now - (_tsB * 1000) - (rtt / 2) + 500) / 1000;
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug(prefix()+"h(X+Y) is correct, skew = " + _peerSkew);
+
+ // the skew is not authenticated yet, but it is certainly fatal to
+ // the establishment, so fail hard if appropriate
+ long diff = 1000*Math.abs(_peerSkew);
+ if (!_context.clock().getUpdatedSuccessfully()) {
+ // Adjust the clock one time in desperation
+ // We are Alice, he is Bob, adjust to match Bob
+ _context.clock().setOffset(1000 * (0 - _peerSkew), true);
+ _peerSkew = 0;
+ if (diff != 0)
+ _log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
+ } else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
+ _context.statManager().addRateData("ntcp.invalidOutboundSkew", diff);
+ _transport.markReachable(_con.getRemotePeer().calculateHash(), false);
+ // Only banlist if we know what time it is
+ _context.banlist().banlistRouter(DataHelper.formatDuration(diff),
+ _con.getRemotePeer().calculateHash(),
+ _x("Excessive clock skew: {0}"));
+ _transport.setLastBadSkew(_peerSkew);
+ fail("Clocks too skewed (" + diff + " ms)", null, true);
+ return;
+ } else if (_log.shouldLog(Log.DEBUG)) {
+ _log.debug(prefix()+"Clock skew: " + diff + " ms");
+ }
+
+ // now prepare and send our response
+ // send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
+ int sigSize = XY_SIZE + XY_SIZE + HXY_SIZE + 4+4;//+12;
+ byte preSign[] = new byte[sigSize];
+ System.arraycopy(_X, 0, preSign, 0, XY_SIZE);
+ System.arraycopy(_Y, 0, preSign, XY_SIZE, XY_SIZE);
+ System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, XY_SIZE + XY_SIZE, HXY_SIZE);
+ DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE, 4, _tsA);
+ DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE + 4, 4, _tsB);
+ // hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
+ Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
+
+ byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
+ // handle variable signature size
+ int min = 2 + ident.length + 4 + sig.length();
+ int rem = min % AES_SIZE;
+ int padding = 0;
+ if (rem > 0)
+ padding = AES_SIZE - rem;
+ byte preEncrypt[] = new byte[min+padding];
+ DataHelper.toLong(preEncrypt, 0, 2, ident.length);
+ System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
+ DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
+ if (padding > 0)
+ _context.random().nextBytes(preEncrypt, 2 + ident.length + 4, padding);
+ System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, sig.length());
+
+ _prevEncrypted = new byte[preEncrypt.length];
+ _context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(),
+ _hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-AES_SIZE, preEncrypt.length);
+
+ changeState(State.OB_SENT_RI);
+ _transport.getPumper().wantsWrite(_con, _prevEncrypted);
}
// Read in message #4
@@ -205,7 +211,7 @@ class OutboundEstablishState extends EstablishBase {
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
src.hasRemaining() + ")");
} else {
- off = _received - XY_SIZE - HXY_TSB_PAD_SIZE;
+ off = _received;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
@@ -223,6 +229,7 @@ class OutboundEstablishState extends EstablishBase {
// handle variable signature size
SigType type = _con.getRemotePeer().getSigningPublicKey().getType();
int siglen = type.getSigLen();
+ // we don't need to do this if no padding!
byte bobSigData[] = new byte[siglen];
System.arraycopy(bobSig, 0, bobSigData, 0, siglen);
Signature sig = new Signature(type, bobSigData);
@@ -242,23 +249,36 @@ class OutboundEstablishState extends EstablishBase {
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "signature verified from Bob. done!");
- prepareExtra(src);
byte nextWriteIV[] = SimpleByteCache.acquire(AES_SIZE);
System.arraycopy(_prevEncrypted, _prevEncrypted.length-AES_SIZE, nextWriteIV, 0, AES_SIZE);
// this does not copy the nextWriteIV, do not release to cache
// We are Alice, he is Bob, clock skew is Bob - Alice
- _con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig); // skew in seconds
+ // skew in seconds
+ _con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig);
+ changeState(State.VERIFIED);
+ if (src.hasRemaining()) {
+ // process "extra" data
+ // This is fairly common for outbound, where Bob may send his updated RI
+ if (_log.shouldInfo())
+ _log.info("extra data " + src.remaining() + " on " + this);
+ _con.recvEncryptedI2NP(src);
+ }
releaseBufs(true);
// if socket gets closed this will be null - prevent NPE
InetAddress ia = _con.getChannel().socket().getInetAddress();
if (ia != null)
_transport.setIP(_con.getRemotePeer().calculateHash(), ia.getAddress());
- changeState(State.VERIFIED);
}
return;
}
}
}
+
+ // check for remaining data
+ if ((_state == State.VERIFIED || _state == State.CORRUPT) && src.hasRemaining()) {
+ if (_log.shouldWarn())
+ _log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
+ }
}
/**
@@ -266,13 +286,12 @@ class OutboundEstablishState extends EstablishBase {
* We are establishing an outbound connection, so prepare ourselves by
* queueing up the write of the first part of the handshake
* This method sends message #1 to Bob.
+ *
+ * @throws IllegalStateException
*/
+ @Override
public synchronized void prepareOutbound() {
- boolean shouldSend;
- synchronized(_stateLock) {
- shouldSend = _state == State.OB_INIT;
- }
- if (shouldSend) {
+ if (_state == State.OB_INIT) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "send X");
byte toWrite[] = new byte[XY_SIZE + _hX_xor_bobIdentHash.length];
@@ -281,8 +300,7 @@ class OutboundEstablishState extends EstablishBase {
changeState(State.OB_SENT_X);
_transport.getPumper().wantsWrite(_con, toWrite);
} else {
- if (_log.shouldLog(Log.WARN))
- _log.warn(prefix() + "unexpected prepareOutbound()");
+ throw new IllegalStateException(prefix() + "unexpected prepareOutbound()");
}
}
@@ -293,6 +311,7 @@ class OutboundEstablishState extends EstablishBase {
@Override
protected void releaseBufs(boolean isVerified) {
super.releaseBufs(isVerified);
+ Arrays.fill(_Y, (byte) 0);
SimpleByteCache.release(_Y);
}
}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/OutboundNTCP2State.java b/router/java/src/net/i2p/router/transport/ntcp/OutboundNTCP2State.java
new file mode 100644
index 000000000..688824f4a
--- /dev/null
+++ b/router/java/src/net/i2p/router/transport/ntcp/OutboundNTCP2State.java
@@ -0,0 +1,520 @@
+package net.i2p.router.transport.ntcp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.southernstorm.noise.protocol.CipherState;
+import com.southernstorm.noise.protocol.CipherStatePair;
+import com.southernstorm.noise.protocol.HandshakeState;
+
+import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Hash;
+import net.i2p.data.SessionKey;
+import net.i2p.data.router.RouterIdentity;
+import net.i2p.data.router.RouterInfo;
+import net.i2p.router.RouterContext;
+import net.i2p.router.transport.ntcp.NTCP2Payload.Block;
+import net.i2p.util.Log;
+
+/**
+ *
+ * NTCP 2 only. We are Alice.
+ *
+ * Also contains static constants and methods used by InboundEstablishState for NTCP2.
+ * Does not extend EstablishBase.
+ *
+ * @since 0.9.35
+ */
+class OutboundNTCP2State implements EstablishState {
+
+ private final RouterContext _context;
+ private final Log _log;
+ private final NTCPTransport _transport;
+ private final NTCPConnection _con;
+ private final byte[] _tmp;
+ /** bytes received so far */
+ private int _received;
+ private long _peerSkew;
+
+ public static final int KEY_SIZE = 32;
+ public static final int MAC_SIZE = 16;
+ public static final int IV_SIZE = 16;
+ public static final int OPTIONS1_SIZE = 16;
+ /** 64 */
+ public static final int MSG1_SIZE = KEY_SIZE + OPTIONS1_SIZE + MAC_SIZE;
+ /** one less than 288 byte NTCP1 msg 1 */
+ public static final int TOTAL1_MAX = 287;
+ private static final int PADDING1_MAX = 64;
+ private static final int PADDING3_MAX = 64;
+ public static final int OPTIONS2_SIZE = 16;
+ public static final int MSG2_SIZE = KEY_SIZE + OPTIONS2_SIZE + MAC_SIZE;
+ /** 48 */
+ public static final int MSG3P1_SIZE = KEY_SIZE + MAC_SIZE;
+ private static final int OPTIONS3_SIZE = 12;
+ /** in SECONDS */
+ public static final long MAX_SKEW = 60;
+ // SipHash KDF things
+ private static final byte[] ZEROLEN = new byte[0];
+ private static final byte[] ONE = new byte[] { 1 };
+ public static final byte[] ZEROKEY = new byte[KEY_SIZE];
+ /** for SipHash keygen */
+ private static final byte[] ASK = new byte[] { (byte) 'a', (byte) 's', (byte) 'k', 1 };
+ /** for SipHash keygen */
+ private static final byte[] SIPHASH = DataHelper.getASCII("siphash");
+
+ private final Object _stateLock = new Object();
+ private State _state;
+
+ private final HandshakeState _handshakeState;
+ private final RouterInfo _aliceRI;
+ private final int _aliceRISize;
+ private int _padlen1;
+ private int _padlen2;
+ private final int _padlen3;
+ private final SessionKey _bobHash;
+ private final byte[] _bobIV;
+
+ private enum State {
+ OB_INIT,
+ /** sent 1 */
+ OB_SENT_X,
+ /** sent 1, got 2 but not padding */
+ OB_GOT_HXY,
+ /** sent 1, got 2 incl. padding */
+ OB_GOT_PADDING,
+ /** sent 1, got 2 incl. padding, sent 3 */
+ VERIFIED,
+ CORRUPT
+ }
+
+ public OutboundNTCP2State(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
+ _context = ctx;
+ _log = ctx.logManager().getLog(getClass());
+ _transport = transport;
+ _con = con;
+ _state = State.OB_INIT;
+ _tmp = new byte[TOTAL1_MAX];
+ try {
+ _handshakeState = new HandshakeState(HandshakeState.INITIATOR, _transport.getXDHFactory());
+ } catch (GeneralSecurityException gse) {
+ throw new IllegalStateException("bad proto", gse);
+ }
+ // save because we must know length
+ _aliceRI = ctx.router().getRouterInfo();
+ if (_aliceRI == null)
+ throw new IllegalStateException("no RI yet");
+ _aliceRISize = _aliceRI.toByteArray().length;
+ _padlen3 = _context.random().nextInt(PADDING3_MAX);
+
+ Hash h = _con.getRemotePeer().calculateHash();
+ _bobHash = new SessionKey(h.getData());
+ String s = _con.getRemoteAddress().getOption("i");
+ if (s == null)
+ throw new IllegalArgumentException("no NTCP2 IV");
+ _bobIV = Base64.decode(s);
+ if (_bobIV == null || _bobIV.length != IV_SIZE ||
+ DataHelper.eq(_bobIV, 0, ZEROKEY, 0, IV_SIZE))
+ throw new IllegalArgumentException("bad NTCP2 IV");
+ }
+
+ private void changeState(State state) {
+ synchronized (_stateLock) {
+ _state = state;
+ }
+ }
+
+ /**
+ * Parse the contents of the buffer as part of the handshake.
+ *
+ * All data must be copied out of the buffer as Reader.processRead()
+ * will return it to the pool.
+ */
+ @Override
+ public synchronized void receive(ByteBuffer src) {
+ if (_state == State.VERIFIED || _state == State.CORRUPT)
+ throw new IllegalStateException(this + "received unexpected data on " + _con);
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug(this + "Receiving: " + src.remaining() + " Received: " + _received);
+ if (!src.hasRemaining())
+ return; // nothing to receive
+ receiveOutbound(src);
+ }
+
+ /** did the handshake fail for some reason? */
+ public boolean isCorrupt() {
+ synchronized (_stateLock) {
+ return _state == State.CORRUPT;
+ }
+ }
+
+ /**
+ * Don't synchronize this, deadlocks all over.
+ *
+ * @return is the handshake complete and valid?
+ */
+ public boolean isComplete() {
+ synchronized (_stateLock) {
+ return _state == State.VERIFIED;
+ }
+ }
+
+ /**
+ * Get the NTCP version
+ * @return 2
+ */
+ public int getVersion() { return 2; }
+
+ /**
+ * We are Alice.
+ * We are establishing an outbound connection, so prepare ourselves by
+ * writing the first message in the handshake.
+ * Encrypt X and write X, the options block, and the padding.
+ * Save last half of encrypted X as IV for message 2 AES.
+ *
+ * @throws IllegalStateException
+ */
+ public synchronized void prepareOutbound() {
+ if (!(_state == State.OB_INIT)) {
+ throw new IllegalStateException(this + "unexpected prepareOutbound()");
+ }
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug(this + "send X");
+ byte options[] = new byte[OPTIONS1_SIZE];
+ options[1] = NTCPTransport.NTCP2_INT_VERSION;
+ int padlen1 = _context.random().nextInt(PADDING1_MAX);
+ DataHelper.toLong(options, 2, 2, padlen1);
+ int msg3p2len = NTCP2Payload.BLOCK_HEADER_SIZE + 1 + _aliceRISize +
+ NTCP2Payload.BLOCK_HEADER_SIZE + OPTIONS3_SIZE +
+ NTCP2Payload.BLOCK_HEADER_SIZE + _padlen3 +
+ MAC_SIZE;
+ DataHelper.toLong(options, 4, 2, msg3p2len);
+ long now = (_context.clock().now() + 500) / 1000;
+ DataHelper.toLong(options, 8, 4, now);
+
+ // set keys
+ String s = _con.getRemoteAddress().getOption("s");
+ if (s == null) {
+ fail("no NTCP2 S");
+ return;
+ }
+ byte[] bk = Base64.decode(s);
+ if (bk == null || bk.length != KEY_SIZE ||
+ DataHelper.eq(bk, 0, ZEROKEY, 0, KEY_SIZE)) {
+ fail("bad NTCP2 S: " + s);
+ return;
+ }
+ _handshakeState.getRemotePublicKey().setPublicKey(bk, 0);
+ _handshakeState.getLocalKeyPair().setPublicKey(_transport.getNTCP2StaticPubkey(), 0);
+ _handshakeState.getLocalKeyPair().setPrivateKey(_transport.getNTCP2StaticPrivkey(), 0);
+ // output to _tmp
+ try {
+ _handshakeState.start();
+ if (_log.shouldWarn())
+ _log.warn("After start: " + _handshakeState.toString());
+ _handshakeState.writeMessage(_tmp, 0, options, 0, OPTIONS1_SIZE);
+ } catch (GeneralSecurityException gse) {
+ // buffer length error
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 1 out", gse);
+ fail("Bad msg 1 out", gse);
+ return;
+ } catch (RuntimeException re) {
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 1 out", re);
+ fail("Bad msg 1 out", re);
+ return;
+ }
+ if (_log.shouldWarn())
+ _log.warn("After msg 1: " + _handshakeState.toString());
+
+ // encrypt key before writing
+ _context.aes().encrypt(_tmp, 0, _tmp, 0, _bobHash, _bobIV, KEY_SIZE);
+ // overwrite _bobIV with last 16 encrypted bytes, CBC for message 2
+ System.arraycopy(_tmp, KEY_SIZE - IV_SIZE, _bobIV, 0, IV_SIZE);
+ // add padding
+ if (padlen1 > 0) {
+ _context.random().nextBytes(_tmp, MSG1_SIZE, padlen1);
+ _handshakeState.mixHash(_tmp, MSG1_SIZE, padlen1);
+ if (_log.shouldWarn())
+ _log.warn("After mixhash padding " + padlen1 + " msg 1: " + _handshakeState.toString());
+ }
+
+ changeState(State.OB_SENT_X);
+ // send it all at once
+ _transport.getPumper().wantsWrite(_con, _tmp, 0, MSG1_SIZE + padlen1);
+ }
+
+ /**
+ * We are Alice, so receive these bytes as part of an outbound connection.
+ * This method receives message 2, and sends message 3.
+ *
+ * IV (CBC from msg 1) must be in _bobIV
+ *
+ * All data must be copied out of the buffer as Reader.processRead()
+ * will return it to the pool.
+ *
+ * Caller must synch
+ */
+ private void receiveOutbound(ByteBuffer src) {
+ // Read in message #2 except for the padding
+ if (_state == State.OB_SENT_X && src.hasRemaining()) {
+ int toGet = Math.min(src.remaining(), MSG2_SIZE - _received);
+ src.get(_tmp, _received, toGet);
+ _received += toGet;
+ if (_received < MSG2_SIZE)
+ return;
+ _context.aes().decrypt(_tmp, 0, _tmp, 0, _bobHash, _bobIV, KEY_SIZE);
+ if (DataHelper.eqCT(_tmp, 0, ZEROKEY, 0, KEY_SIZE)) {
+ fail("Bad msg 2, Y = 0");
+ return;
+ }
+ byte[] options2 = new byte[OPTIONS2_SIZE];
+ try {
+ _handshakeState.readMessage(_tmp, 0, MSG2_SIZE, options2, 0);
+ } catch (GeneralSecurityException gse) {
+ fail("Bad msg 2, Y = " + Base64.encode(_tmp, 0, KEY_SIZE), gse);
+ return;
+ } catch (RuntimeException re) {
+ fail("Bad msg 2, Y = " + Base64.encode(_tmp, 0, KEY_SIZE), re);
+ return;
+ }
+ if (_log.shouldWarn())
+ _log.warn("After msg 2: " + _handshakeState.toString());
+ _padlen2 = (int) DataHelper.fromLong(options2, 2, 2);
+ long tsB = DataHelper.fromLong(options2, 8, 4);
+ long now = _context.clock().now();
+ // rtt from sending #1 to receiving #2
+ long rtt = now - _con.getCreated();
+ _peerSkew = (now - (tsB * 1000) - (rtt / 2) + 500) / 1000;
+ if (_peerSkew > MAX_SKEW || _peerSkew < 0 - MAX_SKEW) {
+ fail("Clock Skew: " + _peerSkew, null, true);
+ return;
+ }
+ changeState(State.OB_GOT_HXY);
+ _received = 0;
+ }
+
+ // Read in the padding for message #2
+ if (_state == State.OB_GOT_HXY && src.hasRemaining()) {
+ int toGet = Math.min(src.remaining(), _padlen2 - _received);
+ src.get(_tmp, _received, toGet);
+ _received += toGet;
+ if (_received < _padlen2)
+ return;
+ if (_padlen2 > 0) {
+ _handshakeState.mixHash(_tmp, 0, _padlen2);
+ if (_log.shouldWarn())
+ _log.warn("After mixhash padding " + _padlen2 + " msg 2: " + _handshakeState.toString());
+ }
+ changeState(State.OB_GOT_PADDING);
+ if (src.hasRemaining()) {
+ // Outbound conn can never have extra data after msg 2
+ fail("Extra data after msg 2: " + src.remaining());
+ return;
+ }
+ prepareOutbound3();
+ return;
+ }
+
+ // check for remaining data
+ if ((_state == State.VERIFIED || _state == State.CORRUPT) && src.hasRemaining()) {
+ if (_log.shouldWarn())
+ _log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
+ }
+ }
+
+ /**
+ * We are Alice.
+ * Write the 3rd message.
+ *
+ * Caller must synch
+ */
+ private void prepareOutbound3() {
+ // create msg 3 part 2 payload
+ // payload without MAC
+ int msg3p2len = NTCP2Payload.BLOCK_HEADER_SIZE + 1 + _aliceRISize +
+ NTCP2Payload.BLOCK_HEADER_SIZE + OPTIONS3_SIZE +
+ NTCP2Payload.BLOCK_HEADER_SIZE + _padlen3;
+
+ // total for parts 1 and 2 with mac
+ byte[] tmp = new byte[MSG3P1_SIZE + msg3p2len + MAC_SIZE];
+ List blocks = new ArrayList(3);
+ Block block = new NTCP2Payload.RIBlock(_aliceRI, false);
+ blocks.add(block);
+ byte[] opts = new byte[OPTIONS3_SIZE];
+ opts[0] = NTCPConnection.PADDING_MIN_DEFAULT_INT;
+ opts[1] = NTCPConnection.PADDING_MAX_DEFAULT_INT;
+ opts[2] = NTCPConnection.PADDING_MIN_DEFAULT_INT;
+ opts[3] = NTCPConnection.PADDING_MAX_DEFAULT_INT;
+ DataHelper.toLong(opts, 4, 2, NTCPConnection.DUMMY_DEFAULT);
+ DataHelper.toLong(opts, 6, 2, NTCPConnection.DUMMY_DEFAULT);
+ DataHelper.toLong(opts, 8, 2, NTCPConnection.DELAY_DEFAULT);
+ DataHelper.toLong(opts, 10, 2, NTCPConnection.DELAY_DEFAULT);
+ block = new NTCP2Payload.OptionsBlock(opts);
+ blocks.add(block);
+ // all zeros is fine here
+ //block = new NTCP2Payload.PaddingBlock(_context, _padlen3);
+ block = new NTCP2Payload.PaddingBlock(_padlen3);
+ blocks.add(block);
+ // we put it at the offset so it doesn't get overwritten by HandshakeState
+ // when it copies the static key in there
+ int newoff = NTCP2Payload.writePayload(tmp, MSG3P1_SIZE, blocks);
+ int expect = MSG3P1_SIZE + msg3p2len;
+ if (newoff != expect)
+ throw new IllegalStateException("msg3 size mismatch expected " + expect + " got " + newoff);
+ try {
+ _handshakeState.writeMessage(tmp, 0, tmp, MSG3P1_SIZE, msg3p2len);
+ } catch (GeneralSecurityException gse) {
+ // buffer length error
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 3 out", gse);
+ fail("Bad msg 3 out", gse);
+ return;
+ } catch (RuntimeException re) {
+ if (!_log.shouldWarn())
+ _log.error("Bad msg 3 out", re);
+ fail("Bad msg 3 out", re);
+ return;
+ }
+ // send it all at once
+ if (_log.shouldWarn())
+ _log.warn("Sending msg3, part 1 is:\n" + net.i2p.util.HexDump.dump(tmp, 0, MSG3P1_SIZE));
+ _transport.getPumper().wantsWrite(_con, tmp);
+ if (_log.shouldWarn())
+ _log.warn("After msg 3: " + _handshakeState.toString());
+ setDataPhase();
+ }
+
+ /**
+ * KDF for data phase,
+ * then calls con.finishOutboundEstablishment(),
+ * passing over the final keys and states to the con.
+ *
+ * Caller must synch
+ */
+ private void setDataPhase() {
+ // Data phase ChaChaPoly keys
+ CipherStatePair ckp = _handshakeState.split();
+ CipherState rcvr = ckp.getReceiver();
+ CipherState sender = ckp.getSender();
+ byte[] k_ab = sender.getKey();
+ byte[] k_ba = rcvr.getKey();
+
+ // Data phase SipHash keys
+ byte[][] sipkeys = generateSipHashKeys(_context, _handshakeState);
+ byte[] sip_ab = sipkeys[0];
+ byte[] sip_ba = sipkeys[1];
+
+ if (_log.shouldWarn()) {
+ _log.warn("Finished establishment for " + this +
+ "\nGenerated ChaCha key for A->B: " + Base64.encode(k_ab) +
+ "\nGenerated ChaCha key for B->A: " + Base64.encode(k_ba) +
+ "\nGenerated SipHash key for A->B: " + Base64.encode(sip_ab) +
+ "\nGenerated SipHash key for B->A: " + Base64.encode(sip_ba));
+ }
+ // skew in seconds
+ _con.finishOutboundEstablishment(sender, rcvr, sip_ab, sip_ba, _peerSkew);
+ changeState(State.VERIFIED);
+ // no extra data possible
+ releaseBufs(true);
+ _handshakeState.destroy();
+ Arrays.fill(sip_ab, (byte) 0);
+ Arrays.fill(sip_ba, (byte) 0);
+ }
+
+ /**
+ * KDF for SipHash
+ *
+ * @return rv[0] is sip_ab, rv[1] is sip_ba
+ */
+ static byte[][] generateSipHashKeys(RouterContext ctx, HandshakeState state) {
+ // TODO use noise HMAC or HKDF method instead?
+ // ask_master = HKDF(ck, zerolen, info="ask")
+ SessionKey tk = new SessionKey(state.getChainingKey());
+ byte[] temp_key = doHMAC(ctx, tk, ZEROLEN);
+ tk = new SessionKey(temp_key);
+ byte[] ask_master = doHMAC(ctx, tk, ASK);
+ byte[] tmp = new byte[32 + SIPHASH.length];
+ byte[] hash = state.getHash();
+ System.arraycopy(hash, 0, tmp, 0, 32);
+ System.arraycopy(SIPHASH, 0, tmp, 32, SIPHASH.length);
+ tk = new SessionKey(ask_master);
+ temp_key = doHMAC(ctx, tk, tmp);
+ tk = new SessionKey(temp_key);
+ byte[] sip_master = doHMAC(ctx, tk, ONE);
+ tk = new SessionKey(sip_master);
+ temp_key = doHMAC(ctx, tk, ZEROLEN);
+ tk = new SessionKey(temp_key);
+ // Output 1
+ byte[] sip_ab = doHMAC(ctx, tk, ONE);
+ // Output 2
+ tmp = new byte[KEY_SIZE + 1];
+ System.arraycopy(sip_ab, 0, tmp, 0, 32);
+ tmp[32] = 2;
+ byte[] sip_ba = doHMAC(ctx, tk, tmp);
+ Arrays.fill(temp_key, (byte) 0);
+ Arrays.fill(tmp, (byte) 0);
+ return new byte[][] { sip_ab, sip_ba };
+ }
+
+ private static byte[] doHMAC(RouterContext ctx, SessionKey key, byte data[]) {
+ byte[] rv = new byte[32];
+ ctx.hmac256().calculate(key, data, 0, data.length, rv, 0);
+ return rv;
+ }
+
+ /**
+ * Release resources on timeout.
+ * @param e may be null
+ * @since 0.9.16
+ */
+ public synchronized void close(String reason, Exception e) {
+ fail(reason, e);
+ }
+
+ protected void fail(String reason) { fail(reason, null); }
+
+ protected void fail(String reason, Exception e) { fail(reason, e, false); }
+
+ protected synchronized void fail(String reason, Exception e, boolean bySkew) {
+ if (_state == State.CORRUPT || _state == State.VERIFIED)
+ return;
+ changeState(State.CORRUPT);
+ if (_log.shouldWarn()) {
+ _log.warn(this + "Failed to establish: " + reason, e);
+ _log.warn("State at failure: " + _handshakeState.toString());
+ }
+ _handshakeState.destroy();
+ if (!bySkew)
+ _context.statManager().addRateData("ntcp.receiveCorruptEstablishment", 1);
+ releaseBufs(false);
+ }
+
+ /**
+ * Only call once.
+ *
+ * Caller must synch
+ */
+ private void releaseBufs(boolean isVerified) {
+ Arrays.fill(_tmp, (byte) 0);
+ // TODO
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder(64);
+ buf.append("OBES2 ");
+ buf.append(System.identityHashCode(this));
+ buf.append(' ').append(_state);
+ if (_con.isEstablished()) buf.append(" established");
+ buf.append(": ");
+ return buf.toString();
+ }
+}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/Reader.java b/router/java/src/net/i2p/router/transport/ntcp/Reader.java
index 1e7746d21..98310042d 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/Reader.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/Reader.java
@@ -149,8 +149,6 @@ class Reader {
if ((buf = con.getNextReadBuf()) == null)
return;
EstablishState est = con.getEstablishState();
- if (_log.shouldLog(Log.DEBUG))
- _log.debug("Processing read buffer as an establishment for " + con + " with [" + est + "]");
if (est.isComplete()) {
// why is it complete yet !con.isEstablished?
@@ -163,20 +161,13 @@ class Reader {
est.receive(buf);
EventPumper.releaseBuf(buf);
if (est.isCorrupt()) {
- if (_log.shouldLog(Log.WARN))
- _log.warn("closing connection on establishment because: " +est.getError(), est.getException());
- if (!est.getFailedBySkew())
- _context.statManager().addRateData("ntcp.receiveCorruptEstablishment", 1);
con.close();
return;
}
- if (est.isComplete() && est.getExtraBytes() != null)
- con.recvEncryptedI2NP(ByteBuffer.wrap(est.getExtraBytes()));
+ // EstablishState is responsible for passing "extra" data to the con
}
while (!con.isClosed() && (buf = con.getNextReadBuf()) != null) {
// decrypt the data and push it into an i2np message
- if (_log.shouldLog(Log.DEBUG))
- _log.debug("Processing read buffer as part of an i2np message (" + buf.remaining() + " bytes)");
con.recvEncryptedI2NP(buf);
EventPumper.releaseBuf(buf);
}