From af5019c8dd52f22fb3e68eed76378b6bbd5773ff Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 15 Mar 2022 07:20:23 -0400 Subject: [PATCH] SSU2: Fixes part 11 Save data messages received before or immediately after session confirmed by queueing them for processing after the PeerState2 is created. The fragments for the first I2NP message from Alice to Bob are frequently lost, this will hopefully fix it. Not fully tested, needs wider network testing. --- .../transport/udp/InboundEstablishState2.java | 48 ++++++++++++++++++- .../router/transport/udp/PacketHandler.java | 40 +++++++++++----- 2 files changed, 75 insertions(+), 13 deletions(-) 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