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 3ad8298d1..a45a43da1 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java @@ -7,6 +7,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.GeneralSecurityException; +import java.util.ArrayList; import java.util.List; import com.southernstorm.noise.protocol.ChaChaPolyCipherState; @@ -56,6 +57,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa private long _skew; private int _mtu; private PeerState2 _pstate; + private List _queuedDataPackets; // testing private static final boolean ENFORCE_TOKEN = true; @@ -434,7 +436,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; } public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; } public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; } - public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; } + public synchronized byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; } public InetSocketAddress getSentAddress() { return _aliceSocketAddress; } @Override @@ -661,9 +663,51 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa * @return null if we have not received the session confirmed */ public synchronized PeerState2 getPeerState() { - _currentState = InboundState.IB_STATE_COMPLETE; + if (_pstate != null) { + _currentState = InboundState.IB_STATE_COMPLETE; + if (_queuedDataPackets != null) { + for (UDPPacket packet : _queuedDataPackets) { + if (_log.shouldWarn()) + _log.warn("Passing possible data " + packet + " to PeerState2: " + this); + _pstate.receivePacket(packet); + packet.release(); + } + _queuedDataPackets.clear(); + } + } return _pstate; } + + /** + * @param packet with header still encrypted + */ + public synchronized void queuePossibleDataPacket(UDPPacket packet) { + if (_pstate == null) { + // case 1, race or out-of-order, queue until we have the peerstate + if (_queuedDataPackets == null) { + _queuedDataPackets = new ArrayList(4); + } else if (_queuedDataPackets.size() >= 10) { + if (_log.shouldWarn()) + _log.warn("Not queueing possible data " + packet + ", too many queued on " + this); + return; + } + if (_log.shouldWarn()) + _log.warn("Queueing possible data " + packet + " on " + this); + // have to copy it because PacketHandler will release + DatagramPacket pkt = packet.getPacket(); + UDPPacket packet2 = UDPPacket.acquire(_context, true); + DatagramPacket pkt2 = packet2.getPacket(); + System.arraycopy(pkt.getData(), pkt.getOffset(), pkt2.getData(), pkt2.getOffset(), pkt.getLength()); + pkt2.setLength(pkt.getLength()); + pkt2.setSocketAddress(pkt.getSocketAddress()); + _queuedDataPackets.add(packet2); + } else { + // case 2, race, decrypt header and pass over + if (_log.shouldWarn()) + _log.warn("Passing possible data " + packet + " to PeerState2: " + this); + _pstate.receivePacket(packet); + } + } @Override public String toString() { diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java index 084796181..1aab6dc66 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -820,8 +820,10 @@ class PacketHandler { if (header == null || header.getVersion() != 2 || header.getNetID() != _networkID) { - if (_log.shouldWarn()) - _log.warn("Does not decrypt as Session Request, Token Request, or Peer Test: " + header); + // typical case, SSU 1 that didn't validate, will be logged at WARN level above + // in group 4 receive packet + //if (_log.shouldDebug()) + // _log.debug("Does not decrypt as Session Request, Token Request, or Peer Test: " + header); return false; } type = header.getType(); @@ -851,27 +853,43 @@ class PacketHandler { // tell establisher? return false; } + if (header.getDestConnID() != state.getRcvConnID()) { + if (_log.shouldWarn()) + _log.warn("Bad Dest Conn id " + header); + return false; + } type = SSU2Util.SESSION_REQUEST_FLAG_BYTE; } else { // Session Confirmed or retransmitted Session Request or Token Request header = SSU2Header.trialDecryptShortHeader(packet, k1, k2); - if (header == null || + if (header == null) { + // too short + if (_log.shouldWarn()) + _log.warn("Failed decrypt Session Confirmed"); + return false; + } + if (header.getDestConnID() != state.getRcvConnID()) { + if (_log.shouldWarn()) + _log.warn("Bad Dest Conn id " + header); + return false; + } + if (header.getPacketNumber() != 0 || header.getType() != SSU2Util.SESSION_CONFIRMED_FLAG_BYTE) { if (_log.shouldWarn()) - _log.warn("Failed decrypt Session Confirmed: " + header); + _log.warn("Queue possible data packet on: " + state); // TODO either attempt to decrypt as a retransmitted // Session Request or Token Request, // or just tell establisher so it can retransmit Session Created or Retry - // Could also be Data messages after (possibly lost or out-of-order) Session Confirmed - return false; + + // Possible ordering issues and races: + // Case 1: Data packets before (possibly lost or out-of-order) Session Confirmed + // Case 2: Data packets after Session Confirmed but it wasn't processed yet + // Queue the packet with the state for processing + state.queuePossibleDataPacket(packet); + return true; } type = SSU2Util.SESSION_CONFIRMED_FLAG_BYTE; } - if (header.getDestConnID() != state.getRcvConnID()) { - if (_log.shouldWarn()) - _log.warn("Bad Dest Conn id " + header); - return false; - } } // all good