diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index d7c782af0..44956a05f 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -29,6 +29,7 @@ import net.i2p.router.util.DecayingHashSet; import net.i2p.router.util.DecayingBloomFilter; import net.i2p.util.Addresses; import net.i2p.util.I2PThread; +import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.VersionComparator; @@ -45,6 +46,12 @@ class EstablishmentManager { private final PacketBuilder _builder; private final int _networkID; + // SSU 2 + private final PacketBuilder2 _builder2; + private final boolean _enableSSU2; + private final Map _outboundTokens; + private final Map _inboundTokens; + /** map of RemoteHostId to InboundEstablishState */ private final ConcurrentHashMap _inboundStates; @@ -138,6 +145,10 @@ class EstablishmentManager { private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.24"; private static final String PROP_DISABLE_EXT_OPTS = "i2np.udp.disableExtendedOptions"; + // SSU 2 + private static final int MAX_TOKENS = 512; + public static final long IB_TOKEN_EXPIRATION = 60*60*1000L; + public EstablishmentManager(RouterContext ctx, UDPTransport transport) { _context = ctx; @@ -145,12 +156,22 @@ class EstablishmentManager { _networkID = ctx.router().getNetworkID(); _transport = transport; _builder = transport.getBuilder(); + _builder2 = transport.getBuilder2(); + _enableSSU2 = _builder2 != null; _inboundStates = new ConcurrentHashMap(); _outboundStates = new ConcurrentHashMap(); _queuedOutbound = new ConcurrentHashMap>(); _liveIntroductions = new ConcurrentHashMap(); _outboundByClaimedAddress = new ConcurrentHashMap(); _outboundByHash = new ConcurrentHashMap(); + if (_enableSSU2) { + _inboundTokens = new LHMCache(MAX_TOKENS); + _outboundTokens = new LHMCache(MAX_TOKENS); + } else { + _inboundTokens = null; + _outboundTokens = null; + } + _activityLock = new Object(); _replayFilter = new DecayingHashSet(ctx, 10*60*1000, 8, "SSU-DH-X"); DEFAULT_MAX_CONCURRENT_ESTABLISH = Math.max(DEFAULT_LOW_MAX_CONCURRENT_ESTABLISH, @@ -1428,6 +1449,86 @@ class EstablishmentManager { } } + //// SSU 2 //// + + /** + * Remember a token that can be used later to connect to the peer + * + * @param token nonzero + * @since 0.9.54 + */ + public void addOutboundToken(Hash peer, long token, long expires) { + if (expires < _context.clock().now()) + return; + Token tok = new Token(token, expires); + synchronized(_outboundTokens) { + _outboundTokens.put(peer, tok); + } + } + + /** + * Get a token to connect to the peer + * + * @return 0 if none available + * @since 0.9.54 + */ + public long getOutboundToken(Hash peer) { + Token tok; + synchronized(_outboundTokens) { + tok = _outboundTokens.remove(peer); + } + if (tok == null) + return 0; + if (tok.expires < _context.clock().now()) + return 0; + return tok.token; + } + + /** + * Remember a token that can be used later for the peer to connect to us + * + * @param token nonzero + * @since 0.9.54 + */ + public void addInboundToken(RemoteHostId peer, long token) { + long expires = _context.clock().now() + IB_TOKEN_EXPIRATION; + Token tok = new Token(token, expires); + synchronized(_inboundTokens) { + _inboundTokens.put(peer, tok); + } + } + + /** + * Is the token from this peer valid? + * + * @return valid + * @since 0.9.54 + */ + public boolean isInboundTokenValid(RemoteHostId peer, long token) { + if (token == 0) + return false; + Token tok; + synchronized(_inboundTokens) { + tok = _inboundTokens.get(peer); + if (tok == null) + return false; + if (tok.token != token) + return false; + _inboundTokens.remove(peer); + } + return tok.expires >= _context.clock().now(); + } + + private static class Token { + public final long token, expires; + public Token(long tok, long exp) { + token = tok; expires = exp; + } + } + + //// End SSU 2 //// + + /** * Driving thread, processing up to one step for an inbound peer and up to * one step for an outbound peer. This is prodded whenever any peer's state diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java index afc9b78d8..01c12e706 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java @@ -50,15 +50,15 @@ class InboundEstablishState { private byte _receivedSignature[]; private boolean _verificationAttempted; // sig not verified - private RouterIdentity _receivedUnconfirmedIdentity; + protected RouterIdentity _receivedUnconfirmedIdentity; // identical to uncomfirmed, but sig now verified - private RouterIdentity _receivedConfirmedIdentity; + protected RouterIdentity _receivedConfirmedIdentity; // general status private final long _establishBegin; //private long _lastReceive; protected long _lastSend; protected long _nextSend; - private final RemoteHostId _remoteHostId; + protected final RemoteHostId _remoteHostId; protected InboundState _currentState; private final Queue _queuedMessages; // count for backoff diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java index c966ff237..e0ae23eff 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java @@ -32,17 +32,21 @@ import net.i2p.util.Log; * @since 0.9.54 */ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payload.PayloadCallback { + private final UDPTransport _transport; private final InetSocketAddress _aliceSocketAddress; private final long _rcvConnID; private final long _sendConnID; private final long _token; - private final long _nextToken; private final HandshakeState _handshakeState; private byte[] _sendHeaderEncryptKey1; private final byte[] _rcvHeaderEncryptKey1; private byte[] _sendHeaderEncryptKey2; private byte[] _rcvHeaderEncryptKey2; + // testing + private static final boolean ENFORCE_TOKEN = false; + + /** * @param localPort Must be our external port, otherwise the signature of the * SessionCreated message will be bad if the external port != the internal port. @@ -51,6 +55,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa public InboundEstablishState2(RouterContext ctx, UDPTransport transport, UDPPacket packet) throws GeneralSecurityException { super(ctx, (InetSocketAddress) packet.getPacket().getSocketAddress()); + _transport = transport; DatagramPacket pkt = packet.getPacket(); _aliceSocketAddress = (InetSocketAddress) pkt.getSocketAddress(); _handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.RESPONDER, transport.getXDHFactory()); @@ -68,9 +73,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa int off = pkt.getOffset(); int len = pkt.getLength(); byte data[] = pkt.getData(); - // fast MSB check for key < 2^255 - if ((data[off + 32 + 32 - 1] & 0x80) != 0) - throw new GeneralSecurityException("Bad PK msg 1"); _rcvConnID = DataHelper.fromLong8(data, off); _sendConnID = DataHelper.fromLong8(data, off + 16); if (_rcvConnID == _sendConnID) @@ -85,7 +87,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa token = ctx.random().nextLong(); } while (token == 0); _token = token; - } else if (type == 0 && token == 0) { // || token not valid + } else if (type == 0 && + (token == 0 || + (ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) { _currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED; _sendHeaderEncryptKey2 = introKey; do { @@ -93,6 +97,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } while (token == 0); _token = token; } else { + // fast MSB check for key < 2^255 + if ((data[off + 32 + 32 - 1] & 0x80) != 0) + throw new GeneralSecurityException("Bad PK msg 1"); // probably don't need again _token = token; _handshakeState.start(); @@ -116,7 +123,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader"); _currentState = InboundState.IB_STATE_REQUEST_RECEIVED; } - _nextToken = ctx.random().nextLong(); packetReceived(); } @@ -149,6 +155,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa System.out.println("Got RI block: " + ri); if (isHandshake) throw new DataFormatException("RI in Sess Req"); + _receivedUnconfirmedIdentity = ri.getIdentity(); List addrs = ri.getTargetAddresses("SSU", "SSU2"); RouterAddress ra = null; for (RouterAddress addr : addrs) { @@ -179,6 +186,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa if (!"2".equals(ra.getOption("v"))) throw new DataFormatException("bad SSU2 v"); + _receivedConfirmedIdentity = _receivedUnconfirmedIdentity; _sendHeaderEncryptKey1 = ik; //_sendHeaderEncryptKey2 calculated below @@ -207,19 +215,25 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } public void gotToken(long token, long expires) { - System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires)); + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); + _transport.getEstablisher().addOutboundToken(_receivedConfirmedIdentity.calculateHash(), token, expires); } public void gotI2NP(I2NPMessage msg) { System.out.println("Got I2NP block: " + msg); if (getState() != InboundState.IB_STATE_CREATED_SENT) throw new IllegalStateException("I2NP in Sess Req"); + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); } public void gotFragment(byte[] data, int off, int len, long messageID, int frag, boolean isLast) throws DataFormatException { System.out.println("Got FRAGMENT block: " + messageID); if (getState() != InboundState.IB_STATE_CREATED_SENT) throw new IllegalStateException("I2NP in Sess Req"); + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); } public void gotACK(long ackThru, int acks, byte[] ranges) { @@ -244,7 +258,15 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa public long getSendConnID() { return _sendConnID; } public long getRcvConnID() { return _rcvConnID; } public long getToken() { return _token; } - public long getNextToken() { return _nextToken; } + public long getNextToken() { + // generate on the fly, this will only be called once + long token; + do { + token = _context.random().nextLong(); + } while (token == 0); + _transport.getEstablisher().addInboundToken(_remoteHostId, token); + return token; + } public HandshakeState getHandshakeState() { return _handshakeState; } public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; } public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; } diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index 698e5b88d..f6cce9f08 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -55,7 +55,7 @@ class OutboundEstablishState { private long _nextSend; protected RemoteHostId _remoteHostId; private final RemoteHostId _claimedAddress; - private final RouterIdentity _remotePeer; + protected final RouterIdentity _remotePeer; private final boolean _allowExtendedOptions; private final boolean _needIntroduction; private final SessionKey _introKey; diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java index 3e945b8d7..0628cb63b 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java @@ -37,7 +37,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl private final long _sendConnID; private final long _rcvConnID; private long _token; - private final long _nextToken; private HandshakeState _handshakeState; private final byte[] _sendHeaderEncryptKey1; private final byte[] _rcvHeaderEncryptKey1; @@ -96,7 +95,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } while (_token == 0); } _rcvConnID = rcid; - _nextToken = ctx.random().nextLong(); byte[] ik = introKey.getData(); _sendHeaderEncryptKey1 = ik; _rcvHeaderEncryptKey1 = ik; @@ -173,7 +171,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } public void gotToken(long token, long expires) { - System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires)); + _transport.getEstablisher().addOutboundToken(_remotePeer.calculateHash(), token, expires); } public void gotI2NP(I2NPMessage msg) { @@ -208,7 +206,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl public long getSendConnID() { return _sendConnID; } public long getRcvConnID() { return _rcvConnID; } public long getToken() { return _token; } - public long getNextToken() { return _nextToken; } + public long getNextToken() { + // generate on the fly, this will only be called once + long token; + do { + token = _context.random().nextLong(); + } while (token == 0); + _transport.getEstablisher().addInboundToken(_remoteHostId, token); + return token; + } public HandshakeState getHandshakeState() { return _handshakeState; } public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; } public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; } diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java index 1fdef299e..5b7170d70 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -795,7 +795,7 @@ class PacketBuilder2 { blocks.add(block); } if (token > 0) { - block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L); + block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION); len += block.getTotalLength(); blocks.add(block); } @@ -923,7 +923,7 @@ class PacketBuilder2 { blocks.add(riblock); if (token > 0) { // TODO only if room - Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L); + Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION); len += block.getTotalLength(); blocks.add(block); } diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState2.java b/router/java/src/net/i2p/router/transport/udp/PeerState2.java index 8b98c9e14..d8e7e1467 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java @@ -223,6 +223,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback } public void gotToken(long token, long expires) { + _transport.getEstablisher().addOutboundToken(_remotePeer, token, expires); } public void gotI2NP(I2NPMessage msg) {