* UDP: Fix major MTU bug introduced in 0.8.9.

- Change large MTU from 1492 to 1484 and small from 608 to 620
      for encryption padding efficiency
    - Enforce sent MTU limit
    - Increase receive buffer size from 1536 to 1572 so that excessive-sized
      packets sent by 0.8.9-0.8.11 routers aren't dropped
    - Limit the max acks in a data packet
    - Limit the duplicate acks in successive data packets
    - Only include acks that will fit in the mtu in a data packet
    - Correctly remove acks from the pending set after they are sent,
      so they aren't sent repeatedly
    - Don't pad data packets unless necessary
    - Debug logging and javadocs
This commit is contained in:
zzz
2011-12-06 21:50:33 +00:00
parent be1d95e991
commit 3bd641abd0
9 changed files with 310 additions and 92 deletions

View File

@@ -1,3 +1,23 @@
2011-12-06 zzz
* Router:
- More refactoring tasks to their own files
- Adjust some thread priorities
* Susimail: Adjust login form sizes
* Tunnels: Increase next hop send timeout
* UDP: Fix major MTU bug introduced in 0.8.9.
- Change large MTU from 1492 to 1484 and small from 608 to 620
for encryption padding efficiency
- Enforce sent MTU limit
- Increase receive buffer size from 1536 to 1572 so that excessive-sized
packets sent by 0.8.9-0.8.11 routers aren't dropped
- Limit the max acks in a data packet
- Limit the duplicate acks in successive data packets
- Only include acks that will fit in the mtu in a data packet
- Correctly remove acks from the pending set after they are sent,
so they aren't sent repeatedly
- Don't pad data packets unless necessary
- Debug logging and javadocs
2011-12-04 zzz
* Console:
- Less icons on configclients.jsp

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 16;
public final static long BUILD = 17;
/** for example "-test" */
public final static String EXTRA = "";

View File

@@ -161,7 +161,7 @@ class ACKSender implements Runnable {
continue;
}
if ( (ackBitfields != null) && (!ackBitfields.isEmpty()) ) {
if (!ackBitfields.isEmpty()) {
_context.statManager().addRateData("udp.sendACKCount", ackBitfields.size(), 0);
if (remaining > 0)
_context.statManager().addRateData("udp.sendACKRemaining", remaining, 0);

View File

@@ -153,29 +153,43 @@ class InboundMessageState {
return new PartialBitfield(_messageId, _fragments);
}
/**
* A true partial bitfield that is not complete.
*/
private static final class PartialBitfield implements ACKBitfield {
private long _bitfieldMessageId;
private boolean _fragmentsReceived[];
private final long _bitfieldMessageId;
private final boolean _fragmentsReceived[];
/**
* @param data each element is non-null or null for received or not
*/
public PartialBitfield(long messageId, Object data[]) {
_bitfieldMessageId = messageId;
boolean fragmentsRcvd[] = null;
for (int i = data.length - 1; i >= 0; i--) {
if (data[i] != null) {
if (_fragmentsReceived == null)
_fragmentsReceived = new boolean[i+1];
_fragmentsReceived[i] = true;
if (fragmentsRcvd == null)
fragmentsRcvd = new boolean[i+1];
fragmentsRcvd[i] = true;
}
}
if (_fragmentsReceived == null)
if (fragmentsRcvd == null)
_fragmentsReceived = new boolean[0];
else
_fragmentsReceived = fragmentsRcvd;
}
public int fragmentCount() { return _fragmentsReceived.length; }
public long getMessageId() { return _bitfieldMessageId; }
public boolean received(int fragmentNum) {
if ( (fragmentNum < 0) || (fragmentNum >= _fragmentsReceived.length) )
return false;
return _fragmentsReceived[fragmentNum];
}
/** @return false always */
public boolean receivedComplete() { return false; }
@Override

View File

@@ -372,11 +372,11 @@ class OutboundMessageFragments {
// ok, simplest possible thing is to always tack on the bitfields if
List<Long> msgIds = peer.getCurrentFullACKs();
if (msgIds == null) msgIds = new ArrayList();
List<ACKBitfield> partialACKBitfields = new ArrayList();
peer.fetchPartialACKs(partialACKBitfields);
int piggybackedPartialACK = partialACKBitfields.size();
// getCurrentFullACKs() already makes a copy, do we need to copy again?
// YES because buildPacket() now removes them (maybe)
List<Long> remaining = new ArrayList(msgIds);
int sparseCount = 0;
UDPPacket rv[] = new UDPPacket[fragments]; //sparse

View File

@@ -4,6 +4,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import net.i2p.I2PAppContext;
@@ -124,6 +125,31 @@ class PacketBuilder {
/** we only talk to people of the right version */
static final int PROTOCOL_VERSION = 0;
/** if no extended options or rekey data, which we don't support = 37 */
public static final int HEADER_SIZE = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE + 1 + 4;
/** not including acks. 46 */
public static final int DATA_HEADER_SIZE = HEADER_SIZE + 9;
/** IPv4 only */
public static final int IP_HEADER_SIZE = 20;
public static final int UDP_HEADER_SIZE = 8;
/** 74 */
public static final int MIN_DATA_PACKET_OVERHEAD = IP_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE;
/**
* Only for data packets. No limit in ack-only packets.
* This directly affects data packet overhead.
*/
private static final int MAX_EXPLICIT_ACKS_LARGE = 9;
/**
* Only for data packets. No limit in ack-only packets.
* This directly affects data packet overhead.
*/
private static final int MAX_EXPLICIT_ACKS_SMALL = 4;
public PacketBuilder(I2PAppContext ctx, UDPTransport transport) {
_context = ctx;
_transport = transport;
@@ -142,18 +168,51 @@ class PacketBuilder {
* This builds a data packet (PAYLOAD_TYPE_DATA).
* See the methods below for the other message types.
*
* Note that while the UDP message spec allows for more than one fragment in a message,
* this method writes exactly one fragment.
* For no fragments use buildAck().
* Multiple fragments in a single packet is not supported.
* Rekeying and extended options are not supported.
*
* Packet format:
*<pre>
* 16 byte MAC
* 16 byte IV
* 1 byte flag
* 4 byte date
* 1 byte flag
* 1 byte explicit ack count IF included
* 4*n byte explict acks IF included
* 1 byte ack bitfield count IF included
* 4*n + ?? ack bitfields IF included
* 1 byte fragment count (always 1)
* 4 byte message ID
* 3 byte fragment info
* n byte fragment
* 0-15 bytes padding
*</pre>
*
* So ignoring the ack bitfields, and assuming we have explicit acks,
* it's (47 + 4*explict acks + padding) added to the
* fragment length.
*
* @param ackIdsRemaining list of messageIds (Long) that should be acked by this packet.
* The list itself is passed by reference, and if a messageId is
* transmitted and the sender does not want the ID to be included
* in subsequent acks, it should be removed from the list. NOTE:
* right now this does NOT remove the IDs, which means it assumes
* that the IDs will be transmitted potentially multiple times,
* and should otherwise be removed from the list.
* transmitted it will be removed from the list.
* Not all message IDs will necessarily be sent, there may not be room.
* non-null.
*
* @param partialACKsRemaining list of messageIds (ACKBitfield) that should be acked by this packet.
* The list itself is passed by reference, and if a messageId is
* included, it should be removed from the list.
* Full acks in this list are skipped, they are NOT transmitted.
* non-null.
* Not all acks will necessarily be sent, there may not be room.
*
* @return null on error
*/
public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer, List<Long> ackIdsRemaining, List<ACKBitfield> partialACKsRemaining) {
public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer,
List<Long> ackIdsRemaining, List<ACKBitfield> partialACKsRemaining) {
UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4));
byte data[] = packet.getPacket().getData();
int off = HEADER_SIZE;
@@ -162,48 +221,93 @@ class PacketBuilder {
boolean acksIncluded = false;
if (_log.shouldLog(Log.INFO)) {
msg = new StringBuilder(128);
msg.append("Send to ").append(peer.getRemotePeer().toBase64());
msg.append(" msg ").append(state.getMessageId()).append(":").append(fragment);
if (fragment == state.getFragmentCount() - 1)
msg.append("*");
msg.append("Data pkt to ").append(peer.getRemotePeer().toBase64());
msg.append(" msg ").append(state.getMessageId()).append(" frag:").append(fragment);
msg.append('/').append(state.getFragmentCount());
}
int dataSize = state.fragmentSize(fragment);
if (dataSize < 0) {
packet.release();
return null;
}
int currentMTU = peer.getMTU();
int availableForAcks = currentMTU - MIN_DATA_PACKET_OVERHEAD - dataSize;
int availableForExplicitAcks = availableForAcks;
// ok, now for the body...
// just always ask for an ACK for now...
data[off] |= UDPPacket.DATA_FLAG_WANT_REPLY;
// we should in theory only include explicit ACKs if the expected packet size
// is under the MTU, but for now, since the # of packets acked is so few (usually
// just one or two), and since the packets are so small anyway, an additional five
// or ten bytes doesn't hurt.
if ( (ackIdsRemaining != null) && (!ackIdsRemaining.isEmpty()) )
// partial acks have priority but they are after explicit acks in the packet
// so we have to compute the space in advance
int partialAcksToSend = 0;
if (availableForExplicitAcks >= 6 && !partialACKsRemaining.isEmpty()) {
for (ACKBitfield bf : partialACKsRemaining) {
if (bf.receivedComplete())
continue;
int acksz = 4 + (bf.fragmentCount() / 7) + 1;
if (partialAcksToSend == 0)
acksz++; // ack count
if (availableForExplicitAcks >= acksz) {
availableForExplicitAcks -= acksz;
partialAcksToSend++;
} else {
break;
}
}
if (partialAcksToSend > 0)
data[off] |= UDPPacket.DATA_FLAG_ACK_BITFIELDS;
}
// Only include acks if we have at least 5 bytes available and at least
// one ack is requested.
if (availableForExplicitAcks >= 5 && !ackIdsRemaining.isEmpty()) {
data[off] |= UDPPacket.DATA_FLAG_EXPLICIT_ACK;
if ( (partialACKsRemaining != null) && (!partialACKsRemaining.isEmpty()) )
data[off] |= UDPPacket.DATA_FLAG_ACK_BITFIELDS;
}
off++;
if ( (ackIdsRemaining != null) && (!ackIdsRemaining.isEmpty()) ) {
DataHelper.toLong(data, off, 1, ackIdsRemaining.size());
if (msg != null) {
msg.append(" data: ").append(dataSize).append(" bytes, mtu: ")
.append(currentMTU).append(", ")
.append(ackIdsRemaining.size()).append(" full acks requested, ")
.append(partialACKsRemaining.size()).append(" partial acks requested, ")
.append(availableForAcks).append(" avail. for all acks, ")
.append(availableForExplicitAcks).append(" for full acks, ");
}
int explicitToSend = Math.min(currentMTU > PeerState.MIN_MTU ? MAX_EXPLICIT_ACKS_LARGE : MAX_EXPLICIT_ACKS_SMALL,
Math.min((availableForExplicitAcks - 1) / 4, ackIdsRemaining.size()));
if (explicitToSend > 0) {
if (msg != null)
msg.append(explicitToSend).append(" full acks included:");
DataHelper.toLong(data, off, 1, explicitToSend);
off++;
for (int i = 0; i < ackIdsRemaining.size(); i++) {
//while (ackIdsRemaining.size() > 0) {
Long ackId = ackIdsRemaining.get(i);//(Long)ackIdsRemaining.remove(0);
Iterator<Long> iter = ackIdsRemaining.iterator();
for (int i = 0; i < explicitToSend && iter.hasNext(); i++) {
Long ackId = iter.next();
iter.remove();
// NPE here, how did a null get in the List?
DataHelper.toLong(data, off, 4, ackId.longValue());
off += 4;
if (msg != null) // logging it
msg.append(" full ack: ").append(ackId.longValue());
acksIncluded = true;
}
//acksIncluded = true;
}
if ( (partialACKsRemaining != null) && (!partialACKsRemaining.isEmpty()) ) {
if (partialAcksToSend > 0) {
if (msg != null)
msg.append(partialAcksToSend).append(" partial acks included:");
int origNumRemaining = partialACKsRemaining.size();
int numPartialOffset = off;
// leave it blank for now, since we could skip some
off++;
for (int i = 0; i < partialACKsRemaining.size(); i++) {
ACKBitfield bitfield = partialACKsRemaining.get(i);
Iterator<ACKBitfield> iter = partialACKsRemaining.iterator();
for (int i = 0; i < partialAcksToSend && iter.hasNext(); i++) {
ACKBitfield bitfield = iter.next();
if (bitfield.receivedComplete()) continue;
DataHelper.toLong(data, off, 4, bitfield.getMessageId());
off += 4;
@@ -219,18 +323,17 @@ class PacketBuilder {
}
off++;
}
partialACKsRemaining.remove(i);
iter.remove();
if (msg != null) // logging it
msg.append(" partial ack: ").append(bitfield);
acksIncluded = true;
i--;
}
//acksIncluded = true;
// now jump back and fill in the number of bitfields *actually* included
DataHelper.toLong(data, numPartialOffset, 1, origNumRemaining - partialACKsRemaining.size());
}
if ( (msg != null) && (acksIncluded) )
_log.debug(msg.toString());
//if ( (msg != null) && (acksIncluded) )
// _log.debug(msg.toString());
DataHelper.toLong(data, off, 1, 1); // only one fragment in this message
off++;
@@ -243,57 +346,66 @@ class PacketBuilder {
data[off] |= 1; // isLast
off++;
int size = state.fragmentSize(fragment);
if (size < 0) {
packet.release();
return null;
}
DataHelper.toLong(data, off, 2, size);
DataHelper.toLong(data, off, 2, dataSize);
data[off] &= (byte)0x3F; // 2 highest bits are reserved
off += 2;
int sizeWritten = state.writeFragment(data, off, fragment);
if (sizeWritten != size) {
if (sizeWritten != dataSize) {
if (sizeWritten < 0) {
// probably already freed from OutboundMessageState
if (_log.shouldLog(Log.WARN))
_log.warn("Write failed for fragment " + fragment + " of " + state.getMessageId());
} else {
_log.error("Size written: " + sizeWritten + " but size: " + size
_log.error("Size written: " + sizeWritten + " but size: " + dataSize
+ " for fragment " + fragment + " of " + state.getMessageId());
}
packet.release();
return null;
} else if (_log.shouldLog(Log.DEBUG))
_log.debug("Size written: " + sizeWritten + " for fragment " + fragment
+ " of " + state.getMessageId());
size = sizeWritten;
if (size < 0) {
packet.release();
return null;
//} else if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Size written: " + sizeWritten + " for fragment " + fragment
// + " of " + state.getMessageId());
}
off += size;
off += dataSize;
// we can pad here if we want, maybe randomized?
// pad up so we're on the encryption boundary
int padSize = 16 - (off % 16);
if (padSize > 0) {
// we could do additional random padding here if desired
int mod = off % 16;
if (mod > 0) {
int padSize = 16 - mod;
_context.random().nextBytes(data, off, padSize);
off += padSize;
}
packet.getPacket().setLength(off);
authenticate(packet, peer.getCurrentCipherKey(), peer.getCurrentMACKey());
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
if (_log.shouldLog(Log.INFO)) {
msg.append(" pkt size ").append(off + (IP_HEADER_SIZE + UDP_HEADER_SIZE));
_log.info(msg.toString());
}
// the packet could have been built before the current mtu got lowered, so
// compare to LARGE_MTU
if (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
_log.error("Size is " + off + " for " + packet +
" fragment " + fragment +
" data size " + dataSize +
" pkt size " + (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE)) +
" MTU " + currentMTU +
' ' + availableForAcks + " for all acks " +
availableForExplicitAcks + " for full acks " +
explicitToSend + " full acks included " +
partialAcksToSend + " partial acks included " +
" OMS " + state, new Exception());
}
return packet;
}
/**
* An ACK packet with no acks.
* We use this for keepalive purposes.
* It doesn't generate a reply, but that's ok.
*/
@@ -307,6 +419,7 @@ class PacketBuilder {
* Build the ack packet. The list need not be sorted into full and partial;
* this method will put all fulls before the partials in the outgoing packet.
* An ack packet is just a data packet with no data.
* See buildPacket() for format.
*
* @param ackBitfields list of ACKBitfield instances to either fully or partially ACK
*/
@@ -903,7 +1016,7 @@ class PacketBuilder {
// challenge...
DataHelper.toLong(data, off, 1, 0);
off++;
off += 0; // *cough*
//off += 0; // *cough*
System.arraycopy(ourIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
off += SessionKey.KEYSIZE_BYTES;
@@ -1059,9 +1172,6 @@ class PacketBuilder {
return packet;
}
/** if no extended options or rekey data, which we don't support */
private static final int HEADER_SIZE = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE + 1 + 4;
/**
* Create a new packet and add the flag byte and the time stamp.
* Caller should add data starting at HEADER_SIZE.

View File

@@ -216,6 +216,7 @@ class PeerState {
private static final int DEFAULT_SEND_WINDOW_BYTES = 8*1024;
private static final int MINIMUM_WINDOW_BYTES = DEFAULT_SEND_WINDOW_BYTES;
private static final int MAX_SEND_WINDOW_BYTES = 1024*1024;
/*
* 596 gives us 588 IP byes, 568 UDP bytes, and with an SSU data message,
* 522 fragment bytes, which is enough to send a tunnel data message in 2
@@ -228,9 +229,15 @@ class PeerState {
* 1 + (4 * MAX_RESEND_ACKS_SMALL) which can take up a significant amount of space.
* We reduce the max acks when using the small MTU but it may not be enough...
*
* Goal: VTBM msg fragments 2646 / (620 - 87) fits nicely.
*
* Assuming that we can enforce an MTU correctly, this % 16 should be 12,
* as the IP/UDP header is 28 bytes and data max should be mulitple of 16 for padding efficiency,
* and so PacketBuilder.buildPacket() works correctly.
*/
private static final int MIN_MTU = 608;//600; //1500;
public static final int MIN_MTU = 620;
private static final int DEFAULT_MTU = MIN_MTU;
/*
* based on measurements, 1350 fits nearly all reasonably small I2NP messages
* (larger I2NP messages may be up to 1900B-4500B, which isn't going to fit
@@ -241,8 +248,16 @@ class PeerState {
* 2646 / 2 = 1323
* 1323 + 74 + 46 + 1 + (4 * 9) = 1480
* So why not make it 1492 (old ethernet is 1492, new is 1500)
* Changed to 1492 in 0.8.9
*
* BUT through 0.8.11,
* size estimate was bad, actual packet was up to 48 bytes bigger
* To be figured out. Curse the ACKs.
* Assuming that we can enforce an MTU correctly, this % 16 should be 12,
* as the IP/UDP header is 28 bytes and data max should be mulitple of 16 for padding efficiency,
* and so PacketBuilder.buildPacket() works correctly.
*/
private static final int LARGE_MTU = 1492;
public static final int LARGE_MTU = 1484;
private static final int MIN_RTO = 100 + ACKSender.ACK_FREQUENCY;
private static final int MAX_RTO = 3000; // 5000;
@@ -699,21 +714,41 @@ class PeerState {
* "want to send" list. If the message id is transmitted to the peer,
* removeACKMessage(Long) should be called.
*
* The returned list contains acks not yet sent, followed by
* a random assortment of acks already sent.
* The caller should NOT transmit all of them all the time,
* even if there is room,
* or the packets will have way too much overhead.
*
* @return a new list, do as you like with it
*/
public List<Long> getCurrentFullACKs() {
// no such element exception seen here
ArrayList<Long> rv = new ArrayList(_currentACKs);
List<Long> rv = new ArrayList(_currentACKs);
// include some for retransmission
rv.addAll(_currentACKsResend);
List<Long> randomResends = new ArrayList(_currentACKsResend);
Collections.shuffle(randomResends, _context.random());
rv.addAll(randomResends);
if (_log.shouldLog(Log.INFO))
_log.info("Returning " + _currentACKs.size() + " current and " + randomResends.size() + " resend acks");
return rv;
}
/** the ack was sent */
public void removeACKMessage(Long messageId) {
_currentACKs.remove(messageId);
_currentACKsResend.offer(messageId);
// trim down the resends
while (_currentACKsResend.size() > MAX_RESEND_ACKS)
_currentACKsResend.poll();
boolean removed = _currentACKs.remove(messageId);
if (removed) {
// only add if reoved from current, as this may be called for
// acks already in _currentACKsResend.
_currentACKsResend.offer(messageId);
// trim down the resends
while (_currentACKsResend.size() > MAX_RESEND_ACKS)
_currentACKsResend.poll();
if (_log.shouldLog(Log.INFO))
_log.info("Sent ack " + messageId + " now " + _currentACKs.size() + " current and " +
_currentACKsResend.size() + " resend acks");
}
// should we only do this if removed?
_lastACKSend = _context.clock().now();
}
@@ -722,15 +757,13 @@ class PeerState {
*/
private static final int MAX_RESEND_ACKS = 16;
/**
* The number of duplicate acks sent in each messge -
* Warning, this directly affects network overhead
* Was 16 but that's too much (64 bytes in a max 608 byte packet,
* and often much smaller)
* The max number of duplicate acks sent in each ack-only messge.
* Doesn't really matter, we have plenty of room...
* @since 0.7.13
*/
private static final int MAX_RESEND_ACKS_LARGE = 9;
private static final int MAX_RESEND_ACKS_LARGE = MAX_RESEND_ACKS;
/** for small MTU */
private static final int MAX_RESEND_ACKS_SMALL = 4;
private static final int MAX_RESEND_ACKS_SMALL = MAX_RESEND_ACKS;
/**
* grab a list of ACKBitfield instances, some of which may fully
@@ -740,9 +773,18 @@ class PeerState {
* ACKed with this call. Be sure to check getWantedACKSendSince() which
* will be unchanged if there are ACKs remaining.
*
* @return non-null, possibly empty
* @deprecated unused
*/
public List<ACKBitfield> retrieveACKBitfields() { return retrieveACKBitfields(true); }
/**
* See above. Only called by ACKSender with alwaysIncludeRetransmissions = false.
* So this is only for ACK-only packets, so all the size limiting is useless.
* FIXME.
*
* @return non-null, possibly empty
*/
public List<ACKBitfield> retrieveACKBitfields(boolean alwaysIncludeRetransmissions) {
List<ACKBitfield> rv = new ArrayList(MAX_RESEND_ACKS);
int bytesRemaining = countMaxACKData();
@@ -821,13 +863,17 @@ class PeerState {
}
_lastACKSend = _context.clock().now();
if (rv == null)
rv = Collections.EMPTY_LIST;
//if (rv == null)
// rv = Collections.EMPTY_LIST;
if (partialIncluded > 0)
_context.statManager().addRateData("udp.sendACKPartial", partialIncluded, rv.size() - partialIncluded);
return rv;
}
/**
* @param rv out parameter, populated with true partial ACKBitfields.
* no full bitfields are included.
*/
void fetchPartialACKs(List<ACKBitfield> rv) {
InboundMessageState states[] = null;
int curState = 0;
@@ -853,6 +899,7 @@ class PeerState {
}
}
if (states != null) {
// _inboundMessages is a Map (unordered), so why bother going backwards?
for (int i = curState-1; i >= 0; i--) {
if (states[i] != null)
rv.add(states[i].createACKBitfield());
@@ -860,10 +907,14 @@ class PeerState {
}
}
/** represent a full ACK of a message */
/**
* A dummy "partial" ack which represents a full ACK of a message
*/
private static class FullACKBitfield implements ACKBitfield {
private long _msgId;
private final long _msgId;
public FullACKBitfield(long id) { _msgId = id; }
public int fragmentCount() { return 0; }
public long getMessageId() { return _msgId; }
public boolean received(int fragmentNum) { return true; }
@@ -1062,6 +1113,7 @@ class PeerState {
public long getLastACKSend() { return _lastACKSend; }
public void setLastACKSend(long when) { _lastACKSend = when; }
public long getWantedACKSendSince() { return _wantACKSendSince; }
public boolean unsentACKThresholdReached() {
int threshold = countMaxACKData() / 4;
return _currentACKs.size() >= threshold;
@@ -1070,8 +1122,8 @@ class PeerState {
/** @return MTU - 83 */
private int countMaxACKData() {
return _mtu
- IP_HEADER_SIZE
- UDP_HEADER_SIZE
- PacketBuilder.IP_HEADER_SIZE
- PacketBuilder.UDP_HEADER_SIZE
- UDPPacket.IV_SIZE
- UDPPacket.MAC_SIZE
- 1 // type flag
@@ -1335,16 +1387,22 @@ class PeerState {
*/
private static final boolean THROTTLE_INITIAL_SEND = true;
private static final int SSU_HEADER_SIZE = 46;
static final int UDP_HEADER_SIZE = 8;
static final int IP_HEADER_SIZE = 20;
/**
* Always leave room for this many explicit acks.
* Only for data packets. Does not affect ack-only packets.
* This directly affects data packet overhead, adjust with care.
*/
private static final int MIN_EXPLICIT_ACKS = 3;
/** this is room for three explicit acks or two partial acks or one of each = 13 */
private static final int MIN_ACK_SIZE = 1 + (4 * MIN_EXPLICIT_ACKS);
/**
* how much payload data can we shove in there?
* @return MTU - 74
* @return MTU - 87, i.e. 521 or 1401
*/
private static final int fragmentSize(int mtu) {
return mtu - SSU_HEADER_SIZE - UDP_HEADER_SIZE - IP_HEADER_SIZE;
// 46 + 20 + 8 + 13 = 74 + 13 = 87
return mtu - (PacketBuilder.MIN_DATA_PACKET_OVERHEAD + MIN_ACK_SIZE);
}
private boolean locked_shouldSend(OutboundMessageState state) {

View File

@@ -56,8 +56,14 @@ class UDPPacket {
* if a received packet is this big it is truncated.
* This is bigger than PeerState.LARGE_MTU, as the far-end's
* LARGE_MTU may be larger than ours.
*
* Due to longstanding bugs, a packet may be larger than LARGE_MTU
* (acks and padding). Together with an increase in the LARGE_MTU to
* 1492 in release 0.8.9, routers from 0.8.9 - 0.8.11 can generate
* packets up to 1536. Data packets are always a multiple of 16,
* so make this 4 + a multiple of 16.
*/
static final int MAX_PACKET_SIZE = 1536;
static final int MAX_PACKET_SIZE = 1572;
public static final int IV_SIZE = 16;
public static final int MAC_SIZE = 16;
@@ -116,12 +122,16 @@ class UDPPacket {
_released = false;
}
/****
public void writeData(byte src[], int offset, int len) {
verifyNotReleased();
System.arraycopy(src, offset, _data, 0, len);
_packet.setLength(len);
resetBegin();
}
****/
/** */
public DatagramPacket getPacket() { verifyNotReleased(); return _packet; }
public short getPriority() { verifyNotReleased(); return _priority; }
public long getExpiration() { verifyNotReleased(); return _expiration; }
@@ -263,7 +273,10 @@ class UDPPacket {
buf.append(" byte packet with ");
buf.append(_packet.getAddress().getHostAddress()).append(":");
buf.append(_packet.getPort());
buf.append(" id=").append(System.identityHashCode(this));
//buf.append(" id=").append(System.identityHashCode(this));
buf.append(" msg type=").append(_messageType);
buf.append(" mark type=").append(_markedType);
buf.append(" frag count=").append(_fragmentCount);
buf.append(" sinceEnqueued=").append((_enqueueTime > 0 ? _context.clock().now()-_enqueueTime : -1));
buf.append(" sinceReceived=").append((_receivedTime > 0 ? _context.clock().now()-_receivedTime : -1));

View File

@@ -158,6 +158,9 @@ class UDPSender {
public int add(UDPPacket packet) {
if (packet == null || !_keepRunning) return 0;
int size = 0;
int psz = packet.getPacket().getLength();
if (psz > PeerState.LARGE_MTU)
_log.error("Sending large UDP packet " + psz + " bytes: " + packet);
_outboundQueue.offer(packet);
//size = _outboundQueue.size();
//_context.statManager().addRateData("udp.sendQueueSize", size, lifetime);
@@ -187,7 +190,7 @@ class UDPSender {
_log.debug("Packet to send known: " + packet);
long acquireTime = _context.clock().now();
int size = packet.getPacket().getLength();
int size2 = packet.getPacket().getLength();
// ?? int size2 = packet.getPacket().getLength();
if (size > 0) {
//_context.bandwidthLimiter().requestOutbound(req, size, "UDP sender");
req = _context.bandwidthLimiter().requestOutbound(size, "UDP sender");