forked from I2P_Developers/i2p.i2p
SSU2: Fragmented Session Confirmed
Revert related parts of "Prep for fragmented RI", we are now fragmenting Session Confirmed instead. Fragment and send multiple Session Confirmed packets if required Reassemble Session Confirmed packets Don't process ack block identical to previous received Log tweaks bump -12
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
2022-04-05 zzz
|
||||
* SSU2: Fragmented Session Confirmed
|
||||
|
||||
2022-03-27 zzz
|
||||
* Crypto: Fix CertUtil loading EdDSA certs, check sigs
|
||||
* Router: Validate family sig at startup
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Git";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 11;
|
||||
public final static long BUILD = 12;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
||||
@@ -1100,6 +1100,7 @@ class EstablishmentManager {
|
||||
_context.statManager().addRateData("udp.outboundEstablishTime", state.getLifetime());
|
||||
DatabaseStoreMessage dbsm = null;
|
||||
if (version == 1) {
|
||||
// version 2 sends our RI in handshake
|
||||
if (!state.isFirstMessageOurDSM()) {
|
||||
dbsm = getOurInfo();
|
||||
}
|
||||
@@ -1563,8 +1564,13 @@ class EstablishmentManager {
|
||||
sendCreated(inboundState);
|
||||
break;
|
||||
|
||||
case IB_STATE_CREATED_SENT: // fallthrough
|
||||
// SSU2 only in practice, should only get here if expired
|
||||
case IB_STATE_CONFIRMED_PARTIALLY:
|
||||
if (expired)
|
||||
processExpired(inboundState);
|
||||
break;
|
||||
|
||||
case IB_STATE_CREATED_SENT: // fallthrough
|
||||
case IB_STATE_RETRY_SENT: // SSU2
|
||||
if (expired) {
|
||||
sendDestroy(inboundState);
|
||||
|
||||
@@ -52,6 +52,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
private byte[] _sendHeaderEncryptKey2;
|
||||
private byte[] _rcvHeaderEncryptKey2;
|
||||
private byte[] _sessCrForReTX;
|
||||
private byte[][] _sessConfFragments;
|
||||
private long _timeReceived;
|
||||
// not adjusted for RTT
|
||||
private long _skew;
|
||||
@@ -311,8 +312,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got RI fragment " + frag + " of " + totalFrags);
|
||||
if (isHandshake)
|
||||
throw new IllegalStateException("RI in Sess Req");
|
||||
// not supported, we fragment the whole message now
|
||||
throw new IllegalStateException("fragmented RI");
|
||||
}
|
||||
|
||||
public void gotAddress(byte[] ip, int port) {
|
||||
@@ -521,12 +522,16 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive the last message in the handshake, and create the PeerState.
|
||||
* Receive the last messages in the handshake, and create the PeerState.
|
||||
* If the message is fragmented, store the data for reassembly and return,
|
||||
* unless this was the last one.
|
||||
*
|
||||
* @return the new PeerState2, may also be retrieved from getPeerState()
|
||||
* @return the new PeerState2 if are done, may also be retrieved from getPeerState(),
|
||||
* or null if more fragments to go
|
||||
*/
|
||||
public synchronized PeerState2 receiveSessionConfirmed(UDPPacket packet) throws GeneralSecurityException {
|
||||
if (_currentState != InboundState.IB_STATE_CREATED_SENT)
|
||||
if (_currentState != InboundState.IB_STATE_CREATED_SENT &&
|
||||
_currentState != InboundState.IB_STATE_CONFIRMED_PARTIALLY)
|
||||
throw new GeneralSecurityException("Bad state for Session Confirmed: " + _currentState);
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
SocketAddress from = pkt.getSocketAddress();
|
||||
@@ -538,7 +543,74 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
long rid = DataHelper.fromLong8(data, off);
|
||||
if (rid != _rcvConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: req: " + _rcvConnID + " conf: " + rid);
|
||||
_handshakeState.mixHash(data, off, 16);
|
||||
byte fragbyte = data[off + SHORT_HEADER_FLAGS_OFFSET];
|
||||
int frag = (fragbyte >> 4) & 0x0f;
|
||||
// allow both 0/0 (development) and 0/1 to indicate sole fragment
|
||||
int totalfrag = fragbyte & 0x0f;
|
||||
if (totalfrag > 0 && frag > totalfrag - 1)
|
||||
throw new GeneralSecurityException("Bad sess conf fragment " + frag + " of " + totalfrag);
|
||||
if (totalfrag > 1) {
|
||||
// Fragment processing. Save fragment.
|
||||
// If we have all fragments, reassemble and continue,
|
||||
// else return to await more.
|
||||
if (_sessConfFragments == null) {
|
||||
_sessConfFragments = new byte[totalfrag][];
|
||||
// change state so we will no longer retransmit session created
|
||||
_currentState = InboundState.IB_STATE_CONFIRMED_PARTIALLY;
|
||||
_sessCrForReTX = null;
|
||||
// force past expiration, we don't have anything to send until we have everything
|
||||
_nextSend = _lastSend + 60*1000;
|
||||
} else {
|
||||
if (_sessConfFragments.length != totalfrag) // total frag changed
|
||||
throw new GeneralSecurityException("Bad sess conf fragment " + frag + " of " + totalfrag);
|
||||
if (_sessConfFragments[frag] != null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got dup sess conf frag " + frag + " on " + this);
|
||||
// there is no facility to ack individual fragments
|
||||
//packetReceived();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got sess conf frag " + frag + " len " + len + " on " + this);
|
||||
byte[] fragdata;
|
||||
if (frag == 0) {
|
||||
// preserve header
|
||||
fragdata = new byte[len];
|
||||
System.arraycopy(data, off, fragdata, 0, len);
|
||||
} else {
|
||||
// discard header
|
||||
len -= SHORT_HEADER_SIZE;
|
||||
fragdata = new byte[len];
|
||||
System.arraycopy(data, off + SHORT_HEADER_SIZE, fragdata, 0, len);
|
||||
}
|
||||
_sessConfFragments[frag] = fragdata;
|
||||
int totalsize = 0;
|
||||
for (int i = 0; i < totalfrag; i++) {
|
||||
if (_sessConfFragments[i] == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Still missing at least one sess conf frag on " + this);
|
||||
// there is no facility to ack individual fragments
|
||||
//packetReceived();
|
||||
return null;
|
||||
}
|
||||
totalsize += _sessConfFragments[i].length;
|
||||
}
|
||||
// we have all the fragments
|
||||
// make a jumbo packet and process it through noise
|
||||
len = totalsize;
|
||||
off = 0;
|
||||
data = new byte[len];
|
||||
int joff = 0;
|
||||
for (int i = 0; i < totalfrag; i++) {
|
||||
byte[] f = _sessConfFragments[i];
|
||||
System.arraycopy(f, 0, data, joff, f.length);
|
||||
joff += f.length;
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Have all " + totalfrag + " sess conf frags, total length " + len + " on " + this);
|
||||
}
|
||||
_handshakeState.mixHash(data, off, SHORT_HEADER_SIZE);
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("State after mixHash 3: " + _handshakeState);
|
||||
|
||||
@@ -637,7 +709,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if not sent or already got the session created
|
||||
* @return null if not sent or already got the session confirmed
|
||||
*/
|
||||
public synchronized UDPPacket getRetransmitSessionCreatedPacket() {
|
||||
if (_sessCrForReTX == null)
|
||||
|
||||
@@ -52,7 +52,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
private final byte[] _rcvRetryHeaderEncryptKey2;
|
||||
private int _mtu;
|
||||
private byte[] _sessReqForReTX;
|
||||
private byte[] _sessConfForReTX;
|
||||
private byte[][] _sessConfForReTX;
|
||||
private long _timeReceived;
|
||||
// not adjusted for RTT
|
||||
private long _skew;
|
||||
@@ -383,8 +383,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
|
||||
|
||||
_handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after mixHash 2: " + _handshakeState);
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("State after mixHash 2: " + _handshakeState);
|
||||
|
||||
// decrypt in-place
|
||||
try {
|
||||
@@ -394,8 +394,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
_log.debug("Session create error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
|
||||
throw gse;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after sess cr: " + _handshakeState);
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("State after sess cr: " + _handshakeState);
|
||||
_timeReceived = 0;
|
||||
processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true);
|
||||
packetReceived();
|
||||
@@ -462,20 +462,21 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
* note that we just sent the SessionConfirmed packets
|
||||
* and save them for retransmission
|
||||
*
|
||||
* @param riFrags if non-null, the RI was fragmented, and these are the
|
||||
* remaining fragments to be sent in the PeerState.
|
||||
* @return the new PeerState2, may also be retrieved from getPeerState()
|
||||
*/
|
||||
public synchronized PeerState2 confirmedPacketSent(UDPPacket packet, List<SSU2Payload.RIBlock> riFrags) {
|
||||
public synchronized PeerState2 confirmedPacketsSent(UDPPacket[] packets) {
|
||||
if (_sessConfForReTX == null) {
|
||||
// store pkt for retx
|
||||
// only one supported right now
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
_sessConfForReTX = new byte[len];
|
||||
System.arraycopy(data, off, _sessConfForReTX, 0, len);
|
||||
// store pkts for retx
|
||||
_sessConfForReTX = new byte[packets.length][];
|
||||
for (int i = 0; i < packets.length; i++) {
|
||||
DatagramPacket pkt = packets[i].getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
byte[] save = new byte[len];
|
||||
System.arraycopy(data, off, save, 0, len);
|
||||
_sessConfForReTX[i] = save;
|
||||
}
|
||||
if (_rcvHeaderEncryptKey2 == null)
|
||||
_rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
|
||||
|
||||
@@ -499,6 +500,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
sender.initializeKey(d_ab, 0);
|
||||
ChaChaPolyCipherState rcvr = new ChaChaPolyCipherState();
|
||||
rcvr.initializeKey(d_ba, 0);
|
||||
/****
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("split()\nGenerated Chain key: " + Base64.encode(ckd) +
|
||||
"\nGenerated split key for A->B: " + Base64.encode(k_ab) +
|
||||
@@ -509,6 +511,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
"\nIntro key for Bob: " + Base64.encode(_sendHeaderEncryptKey1) +
|
||||
"\nGenerated header key 2 for A->B: " + Base64.encode(h_ab) +
|
||||
"\nGenerated header key 2 for B->A: " + Base64.encode(h_ba));
|
||||
****/
|
||||
_handshakeState.destroy();
|
||||
if (_requestSentCount == 1)
|
||||
_rtt = (int) ( _context.clock().now() - _lastSend );
|
||||
@@ -518,7 +521,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
_sendConnID, _rcvConnID,
|
||||
_sendHeaderEncryptKey1, h_ab, h_ba);
|
||||
_currentState = OutboundState.OB_STATE_CONFIRMED_COMPLETELY;
|
||||
_pstate.confirmedPacketSent(_sessConfForReTX, riFrags);
|
||||
_pstate.confirmedPacketsSent(_sessConfForReTX);
|
||||
// PS2.super adds CLOCK_SKEW_FUDGE that doesn't apply here
|
||||
_pstate.adjustClockSkew(_skew - (_rtt / 2) - PeerState.CLOCK_SKEW_FUDGE);
|
||||
_pstate.setHisMTU(_mtu);
|
||||
|
||||
@@ -490,40 +490,31 @@ class PacketBuilder2 {
|
||||
len = info.length;
|
||||
}
|
||||
|
||||
UDPPacket packets[] = new UDPPacket[1];
|
||||
packets[0] = buildSessionConfirmedPacket(state, numFragments, info, len, gzip);
|
||||
List<SSU2Payload.RIBlock> riFrags;
|
||||
// one big block
|
||||
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(info, 0, info.length,
|
||||
false, gzip, 0, 1);
|
||||
UDPPacket packets[];
|
||||
if (numFragments > 1) {
|
||||
riFrags = new ArrayList<SSU2Payload.RIBlock>(numFragments - 1);
|
||||
int off = len;
|
||||
for (int i = 1; i < numFragments; i++) {
|
||||
if (i == numFragments - 1)
|
||||
len = info.length - off;
|
||||
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(info, off, len,
|
||||
false, gzip, i, numFragments);
|
||||
riFrags.add(block);
|
||||
off += len;
|
||||
}
|
||||
packets = buildSessionConfirmedPackets(state, block);
|
||||
} else {
|
||||
riFrags = null;
|
||||
packets = new UDPPacket[1];
|
||||
packets[0] = buildSessionConfirmedPacket(state, block);
|
||||
}
|
||||
state.confirmedPacketSent(packets[0], riFrags);
|
||||
state.confirmedPacketsSent(packets);
|
||||
return packets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new SessionConfirmed packet for the given peer
|
||||
* Build a single new SessionConfirmed packet for the given peer, unfragmented.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*/
|
||||
private UDPPacket buildSessionConfirmedPacket(OutboundEstablishState2 state, int numFragments, byte ourInfo[], int len, boolean gzip) {
|
||||
private UDPPacket buildSessionConfirmedPacket(OutboundEstablishState2 state, SSU2Payload.RIBlock block) {
|
||||
UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
pkt.setLength(SHORT_HEADER_SIZE);
|
||||
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(ourInfo, 0, len,
|
||||
false, gzip, 0, numFragments);
|
||||
boolean isIPv6 = state.getSentIP().length == 16;
|
||||
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), isIPv6,
|
||||
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), 1, 0, isIPv6,
|
||||
state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), block, state.getNextToken());
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
packet.setMessageType(TYPE_CONF);
|
||||
@@ -531,6 +522,103 @@ class PacketBuilder2 {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all the fragmented SessionConfirmed packets
|
||||
*
|
||||
*/
|
||||
private UDPPacket[] buildSessionConfirmedPackets(OutboundEstablishState2 state, SSU2Payload.RIBlock block) {
|
||||
UDPPacket packet0 = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
|
||||
DatagramPacket pkt = packet0.getPacket();
|
||||
byte[] data0 = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
boolean isIPv6 = state.getSentIP().length == 16;
|
||||
// actually IP and UDP overhead
|
||||
int ipOverhead = (isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE) + UDP_HEADER_SIZE;
|
||||
// first packet, no new token block or padding
|
||||
int overhead = ipOverhead +
|
||||
SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN;
|
||||
int mtu = state.getMTU();
|
||||
int blockSize = block.getTotalLength();
|
||||
// how much of the ri block we can fit in the first packet
|
||||
int first = mtu - overhead;
|
||||
// how much of the ri block we can fit in additional packets
|
||||
int maxAddl = mtu - (ipOverhead + SHORT_HEADER_SIZE);
|
||||
// how much data fits in a packet
|
||||
int max = mtu - ipOverhead;
|
||||
int remaining = blockSize - first;
|
||||
// ensure last packet isn't too small which would corrupt the header decryption
|
||||
int lastPktSize = remaining % max;
|
||||
int addPadding;
|
||||
if (lastPktSize < 24) {
|
||||
addPadding = 3 + 24 - lastPktSize;
|
||||
remaining += addPadding;
|
||||
} else {
|
||||
addPadding = 0;
|
||||
}
|
||||
int count = 1 + ((remaining + maxAddl - 1) / maxAddl);
|
||||
|
||||
// put jumbo into the first packet, we will put data0 back below
|
||||
// TODO if last packet is less than 8 bytes the header decryption will fail, add padding
|
||||
byte[] jumbo = new byte[overhead + addPadding + block.getTotalLength()];
|
||||
System.arraycopy(data0, off, jumbo, 0, SHORT_HEADER_SIZE);
|
||||
pkt.setData(jumbo);
|
||||
pkt.setLength(SHORT_HEADER_SIZE);
|
||||
byte[] hdrKey1 = state.getSendHeaderEncryptKey1();
|
||||
byte[] hdrKey2 = state.getSendHeaderEncryptKey2();
|
||||
encryptSessionConfirmed(packet0, state.getHandshakeState(), state.getMTU(), count, addPadding, isIPv6,
|
||||
hdrKey1, hdrKey2, block, state.getNextToken());
|
||||
int total = pkt.getLength();
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Building " + count + " fragmented session confirmed packets" +
|
||||
" max data: " + max +
|
||||
" RI block size: " + blockSize +
|
||||
" total data size: " + total);
|
||||
|
||||
// fix up packet0 by putting the byte array back
|
||||
// and encrypting the header
|
||||
System.arraycopy(jumbo, 0, data0, off, max);
|
||||
pkt.setData(data0);
|
||||
pkt.setLength(max);
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
SSU2Header.encryptShortHeader(packet0, hdrKey1, hdrKey2);
|
||||
packet0.setMessageType(TYPE_CONF);
|
||||
packet0.setPriority(PRIORITY_HIGH);
|
||||
List<UDPPacket> rv = new ArrayList<UDPPacket>(4);
|
||||
rv.add(packet0);
|
||||
|
||||
// build all the remaining packets
|
||||
// set frag field in header and encrypt headers
|
||||
// these headers are not bound to anything with mixHash(),
|
||||
// but if anything changes the header will not decrypt correctly
|
||||
int pktnum = 0;
|
||||
for (int i = max; i < total; i += max - SHORT_HEADER_SIZE) {
|
||||
// all packets have packet number 0
|
||||
UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
|
||||
pkt = packet.getPacket();
|
||||
byte[] data = pkt.getData();
|
||||
off = pkt.getOffset();
|
||||
int len = Math.min(max - SHORT_HEADER_SIZE, total - i);
|
||||
System.arraycopy(jumbo, i, data, off + SHORT_HEADER_SIZE, len);
|
||||
data[off + SHORT_HEADER_FLAGS_OFFSET] = (byte) (((++pktnum) << 4) | count); // fragment n of numFragments
|
||||
if (len < 24)
|
||||
_log.error("FIXME " + len);
|
||||
pkt.setLength(len + SHORT_HEADER_SIZE);
|
||||
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
packet0.setMessageType(TYPE_CONF);
|
||||
packet0.setPriority(PRIORITY_HIGH);
|
||||
rv.add(packet);
|
||||
}
|
||||
if (_log.shouldInfo()) {
|
||||
for (int i = 0; i < rv.size(); i++) {
|
||||
_log.info("pkt " + i + " size " + rv.get(i).getPacket().getLength());
|
||||
}
|
||||
}
|
||||
if (rv.size() != count)
|
||||
throw new IllegalStateException("Count " + count + " != size " + rv.size());
|
||||
return rv.toArray(new UDPPacket[count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet as Alice, to Bob to begin a peer test.
|
||||
* In-session, message 1.
|
||||
@@ -936,14 +1024,19 @@ class PacketBuilder2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* If numFragments larger than 1, we do NOT encrypt the header here,
|
||||
* that's caller's responsibility.
|
||||
*
|
||||
* @param packet containing only 16 byte header
|
||||
* @param addPadding force-add exactly this size a padding block, for jumbo only
|
||||
*/
|
||||
private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu,
|
||||
private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu, int numFragments, int addPadding,
|
||||
boolean isIPv6, byte[] hdrKey1, byte[] hdrKey2,
|
||||
SSU2Payload.RIBlock riblock, EstablishmentManager.Token token) {
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
data[off + SHORT_HEADER_FLAGS_OFFSET] = (byte) numFragments; // fragment 0 of numFragments
|
||||
mtu -= UDP_HEADER_SIZE;
|
||||
mtu -= isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE;
|
||||
try {
|
||||
@@ -956,10 +1049,17 @@ class PacketBuilder2 {
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
}
|
||||
Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
|
||||
if (block != null) {
|
||||
len += block.getTotalLength();
|
||||
if (addPadding > 0) {
|
||||
// forced padding so last packet isn't too small
|
||||
Block block = new SSU2Payload.PaddingBlock(addPadding - 3);
|
||||
len += addPadding;
|
||||
blocks.add(block);
|
||||
} else {
|
||||
Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
|
||||
if (block != null) {
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
// If we skip past where the static key and 1st MAC will be, we can
|
||||
@@ -983,7 +1083,8 @@ class PacketBuilder2 {
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("After msg 3: " + state);
|
||||
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
|
||||
if (numFragments <= 1)
|
||||
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
private final long _sendConnID;
|
||||
private final long _rcvConnID;
|
||||
private final AtomicInteger _packetNumber = new AtomicInteger();
|
||||
private final AtomicInteger _lastAckHashCode = new AtomicInteger(-1);
|
||||
private final CipherState _sendCha;
|
||||
private final CipherState _rcvCha;
|
||||
private final byte[] _sendHeaderEncryptKey1;
|
||||
@@ -60,8 +61,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
private long _sentMessagesLastExpired;
|
||||
|
||||
// Session Confirmed retransmit
|
||||
private byte[] _sessConfForReTX;
|
||||
private List<SSU2Payload.RIBlock> _riFragsForReTX;
|
||||
private byte[][] _sessConfForReTX;
|
||||
private long _sessConfSentTime;
|
||||
private int _sessConfSentCount;
|
||||
|
||||
@@ -525,10 +525,16 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
}
|
||||
|
||||
public void gotACK(long ackThru, int acks, byte[] ranges) {
|
||||
int hc = ((int) ackThru) ^ (acks << 24) ^ DataHelper.hashCode(ranges);
|
||||
if (_lastAckHashCode.getAndSet(hc) == hc) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got dup ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
|
||||
return;
|
||||
}
|
||||
SSU2Bitfield ackbf;
|
||||
ackbf = SSU2Bitfield.fromACKBlock(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0));
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
|
||||
_log.debug("Got new ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
|
||||
// calls bitSet() below
|
||||
ackbf.forEachAndNot(_ackedMessages, this);
|
||||
}
|
||||
@@ -639,23 +645,13 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
}
|
||||
|
||||
/**
|
||||
* note that we just sent the SessionConfirmed packet
|
||||
* and save it for retransmission.
|
||||
* Note that we just sent the SessionConfirmed packets
|
||||
* and save them for retransmission.
|
||||
*
|
||||
* @param riFrags if non-null, the RI was fragmented, and these are the
|
||||
* remaining fragments to be sent and saved for retransmission.
|
||||
*/
|
||||
public synchronized void confirmedPacketSent(byte[] data, List<SSU2Payload.RIBlock> riFrags) {
|
||||
synchronized void confirmedPacketsSent(byte[][] data) {
|
||||
if (_sessConfForReTX == null)
|
||||
_sessConfForReTX = data;
|
||||
if (riFrags != null) {
|
||||
if (_riFragsForReTX == null)
|
||||
_riFragsForReTX = riFrags;
|
||||
for (SSU2Payload.RIBlock block : riFrags) {
|
||||
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.emptyList(), Collections.singletonList(block), this);
|
||||
_transport.send(pkt);
|
||||
}
|
||||
}
|
||||
_sessConfSentTime = _context.clock().now();
|
||||
_sessConfSentCount++;
|
||||
}
|
||||
@@ -666,26 +662,19 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
private synchronized UDPPacket[] getRetransmitSessionConfirmedPackets() {
|
||||
if (_sessConfForReTX == null)
|
||||
return null;
|
||||
UDPPacket packet = UDPPacket.acquire(_context, false);
|
||||
int count = 1;
|
||||
if (_riFragsForReTX != null)
|
||||
count += _riFragsForReTX.size();
|
||||
UDPPacket[] rv = new UDPPacket[count];
|
||||
rv[0] = packet;
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
System.arraycopy(_sessConfForReTX, 0, data, off, _sessConfForReTX.length);
|
||||
pkt.setLength(_sessConfForReTX.length);
|
||||
pkt.setAddress(_remoteIPAddress);
|
||||
pkt.setPort(_remotePort);
|
||||
packet.setMessageType(PacketBuilder2.TYPE_CONF);
|
||||
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
|
||||
if (_riFragsForReTX != null) {
|
||||
int i = 1;
|
||||
for (SSU2Payload.RIBlock block : _riFragsForReTX) {
|
||||
rv[i++] = _transport.getBuilder2().buildPacket(Collections.emptyList(), Collections.singletonList(block), this);
|
||||
}
|
||||
UDPPacket[] rv = new UDPPacket[_sessConfForReTX.length];
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context, false);
|
||||
rv[i] = packet;
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
System.arraycopy(_sessConfForReTX[i], 0, data, off, _sessConfForReTX[i].length);
|
||||
pkt.setLength(_sessConfForReTX.length);
|
||||
pkt.setAddress(_remoteIPAddress);
|
||||
pkt.setPort(_remotePort);
|
||||
packet.setMessageType(PacketBuilder2.TYPE_CONF);
|
||||
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user