From abb8cbe75d02d16fd8c764bf6b97e9f1a560bd36 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Mon, 21 Dec 2020 10:02:20 -0500 Subject: [PATCH] SSU: Fix partial acks not being sent when there are no 'gaps'. Workaround the bug on the sending side for pre-0.9.49 routers by sending fragments in reverse order. Bug introduced in commit 9c4558d8919bf58a50a8715b6e5605a21f95bab5 Sep 20 2014. This partially reverts that commit. Reported by and adapted from a patch by zlatinb --- .../transport/udp/InboundMessageState.java | 22 ++++++++++++++++--- .../transport/udp/OutboundMessageState.java | 11 ++++++++-- .../router/transport/udp/PacketBuilder.java | 2 -- .../i2p/router/transport/udp/PeerState.java | 3 +-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java index f2209674f7..15a255aad0 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java @@ -226,7 +226,11 @@ class InboundMessageState implements CDQEntry { return _completeSize; } - /** FIXME synch here or PeerState.fetchPartialACKs() */ + /** + * Only call this if not complete. + * TODO remove this, have InboundMessageState implement ACKBitfield. + * FIXME synch here or PeerState.fetchPartialACKs() + */ public ACKBitfield createACKBitfield() { int last = _lastFragment; int sz = (last >= 0) ? last + 1 : _fragments.length; @@ -236,9 +240,12 @@ class InboundMessageState implements CDQEntry { /** * A true partial bitfield that is probably not complete. * fragmentCount() will return 64 if unknown. + * + * TODO remove this, have InboundMessageState implement ACKBitfield. */ private static final class PartialBitfield implements ACKBitfield { private final long _bitfieldMessageId; + private final int _fragmentCount; private final int _ackCount; private final int _highestReceived; // bitfield, 1 for acked @@ -263,6 +270,7 @@ class InboundMessageState implements CDQEntry { } } _fragmentAcks = acks; + _fragmentCount = size; _ackCount = ackCount; _highestReceived = highestReceived; } @@ -274,7 +282,12 @@ class InboundMessageState implements CDQEntry { return 1L << fragment; } - public int fragmentCount() { return _highestReceived + 1; } + /** + * Don't use for serialization since it may be unknown; + * use highestReceived() instead. + * @return 64 if unknown + */ + public int fragmentCount() { return _fragmentCount; } public int ackCount() { return _ackCount; } @@ -288,7 +301,10 @@ class InboundMessageState implements CDQEntry { return (_fragmentAcks & mask(fragmentNum)) != 0; } - public boolean receivedComplete() { return _ackCount == _highestReceived + 1; } + /** + * @return should always be false + */ + public boolean receivedComplete() { return _ackCount == _fragmentCount; } @Override public String toString() { diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java index 4d76978e1f..15401304dc 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java @@ -259,7 +259,8 @@ class OutboundMessageState implements CDPQEntry { int minSendCount = getMinSendCount(); // Allow only the fragments we've sent the least int rv = 0; - for (int i = 0; i < _numFragments; i++) { + // see below for why we go in reverse order + for (int i = _numFragments - 1; i >= 0; i--) { if (needsSending(i) && _fragmentSends[i] == minSendCount) { int sz = fragmentSize(i) + overhead; int tot = rv + sz; @@ -344,7 +345,13 @@ class OutboundMessageState implements CDPQEntry { int minSendCount = getMinSendCount(); int sent = 0; int overhead = _peer.fragmentOverhead(); - for (int i = 0; i < _numFragments; i++) { + // Send in reverse order, + // receiver bug workaround. + // Through 0.9.48, InboundMessageState.PartialBitfield.isComplete() would return true + // if consecutive fragments were complete, and we would not receive partial acks. + // Also, if we send the last fragment first, receiver can be more efficient + // because it knows the size. + for (int i = _numFragments - 1; i >= 0; i--) { if (needsSending(i)) { int count = _fragmentSends[i]; if (count == minSendCount) { diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index d6d9984fc6..f8eba5b786 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -364,8 +364,6 @@ class PacketBuilder { for (ACKBitfield bf : partialACKsRemaining) { if (partialAcksToSend >= ABSOLUTE_MAX_ACKS) break; // ack count - if (bf.receivedComplete()) - continue; // only send what we have to //int acksz = 4 + (bf.fragmentCount() / 7) + 1; int bits = bf.highestReceived() + 1; diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index bdeb8d0b93..ae604a05b6 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -992,8 +992,7 @@ public class PeerState { if (states != null) { for (InboundMessageState ims : states) { ACKBitfield abf = ims.createACKBitfield(); - if (!abf.receivedComplete()) - rv.add(abf); + rv.add(abf); } } } -- GitLab