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.
This commit is contained in:
zzz
2022-03-15 07:20:23 -04:00
parent 46bba0fe71
commit af5019c8dd
2 changed files with 75 additions and 13 deletions

View File

@@ -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<UDPPacket> _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<UDPPacket>(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() {

View File

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