diff --git a/doc/src/developers-guide/ch-tcp.rst b/doc/src/developers-guide/ch-tcp.rst
index 2c35806ae36..2d54d8b5498 100644
--- a/doc/src/developers-guide/ch-tcp.rst
+++ b/doc/src/developers-guide/ch-tcp.rst
@@ -111,10 +111,9 @@ The :ned:`Tcp` module has the following parameters:
receiver buffer capacity (Note: normally, NIC queues should be at
least this size, default is 14*mss)
-- :par:`delayedAcksEnabled` delayed ACK algorithm (RFC 1122)
- enabled/disabled
+- :par:`delayedAcksEnabled` delayed ACK algorithm (RFC 1122) enabled/disabled
-- :par:`nagleEnabled` Nagle’s algorithm (RFC 896) enabled/disabled
+- :par:`nagleEnabled` Nagle's algorithm (RFC 1122) enabled/disabled
- :par:`limitedTransmitEnabled` Limited Transmit algorithm (RFC 3042)
enabled/disabled (can be used for
@@ -123,18 +122,18 @@ The :ned:`Tcp` module has the following parameters:
- :par:`increasedIWEnabled` Increased Initial Window (RFC 3390)
enabled/disabled
-- :par:`sackSupport` Selective Acknowledgment (RFC 2018, 2883, 3517)
+- :par:`sackSupport` Selective Acknowledgment (RFC 2018, 2883, 6675)
support (header option) (SACK will be enabled for a connection if
both endpoints support it)
-- :par:`windowScalingSupport` Window Scale (RFC 1323) support (header
+- :par:`windowScalingSupport` Window Scale (RFC 7323) support (header
option) (WS will be enabled for a connection if both endpoints
support it)
-- :par:`timestampSupport` Timestamps (RFC 1323) support (header option)
+- :par:`timestampSupport` Timestamps (RFC 7323) support (header option)
(TS will be enabled for a connection if both endpoints support it)
-- :par:`mss` Maximum Segment Size (RFC 793) (header option, default is
+- :par:`mss` Maximum Segment Size (RFC 9293) (header option, default is
536)
- :par:`tcpAlgorithmClass` the name of the TCP flavour
@@ -244,7 +243,7 @@ receives a TCP_I_CONNECTION_REFUSED message.
If you do an active OPEN, then send data and close before the connection
has reached ESTABLISHED, the connection will go from SYN_SENT to CLOSED
without actually sending the buffered data. This is consistent with
- RFC 793 but may not be what you would expect.
+ RFC 9293 but may not be what you would expect.
@@ -403,10 +402,10 @@ etc. Because this algorithm does not send duplicate ACKs when it receives
out-of-order segments, it does not work well together with other
algorithms.
-TcpBaseAlg
+TcpAlgorithmBase
~~~~~~~~~~
-The :cpp:`TcpBaseAlg` is the base class of the INET implementation of
+The :cpp:`TcpAlgorithmBase` is the base class of the INET implementation of
Tahoe, Reno, and NewReno. It implements basic TCP algorithms for
adaptive retransmissions, persistence timers, delayed ACKs, Nagle’s
algorithm, Increased Initial Window – EXCLUDING congestion control.
@@ -416,7 +415,7 @@ Delayed ACK
^^^^^^^^^^^
When the :par:`delayedAcksEnabled` parameter is set to true, the
-:cpp:`TcpBaseAlg` applies a 200ms delay before sending ACKs.
+:cpp:`TcpAlgorithmBase` applies a 200ms delay before sending ACKs.
Nagle’s algorithm
^^^^^^^^^^^^^^^^^
@@ -443,7 +442,7 @@ If the :par:`increasedIWEnabled` parameter is true, then the initial
window is increased to 4380 bytes, but at least 2 SMSS and at most 4
SMSS. The congestion window is not updated afterwards; subclasses can
add congestion control by redefining virtual methods of the
-:cpp:`TcpBaseAlg` class in their own class implementation.
+:cpp:`TcpAlgorithmBase` class in their own class implementation.
Duplicate ACKs
^^^^^^^^^^^^^^
@@ -469,7 +468,7 @@ Can be used to demonstrate the effect of lack of congestion control.
TcpTahoe
~~~~~~~~
-The :cpp:`TcpTahoe` algorithm class extends :cpp:`TcpBaseAlg` with *Slow
+The :cpp:`TcpTahoe` algorithm class extends :cpp:`TcpAlgorithmBase` with *Slow
Start*, *Congestion Avoidance*, and *Fast Retransmit* congestion control
algorithms. This algorithm initiates a *Slow Start* when a packet loss
is detected.
diff --git a/doc/src/msgtags.xml b/doc/src/msgtags.xml
index fbde38f8a8f..c64416e9b18 100644
--- a/doc/src/msgtags.xml
+++ b/doc/src/msgtags.xml
@@ -57,6 +57,8 @@
types/msg-inet--aodv--Rreq.html
+ TcpAlgorithmBaseStateVariables
+ inet-tcp-TcpAlgorithmBaseStateVariables.html
UnreachableNode
types/msg-inet--aodv--UnreachableNode.html
diff --git a/doc/src/users-guide/ch-transport.rst b/doc/src/users-guide/ch-transport.rst
index a06d825bd3e..05b1cb8c81b 100644
--- a/doc/src/users-guide/ch-transport.rst
+++ b/doc/src/users-guide/ch-transport.rst
@@ -41,7 +41,7 @@ Overview
The TCP protocol is the most widely used protocol of the Internet. It
provides reliable, ordered delivery of a stream of bytes from one
application on one computer to another application on another computer.
-The baseline TCP protocol is described in RFC793, but other tens of RFCs
+The baseline TCP protocol is described in RFC 9293, but other tens of RFCs
contain modifications and extensions to TCP. As a result, TCP is a
complex protocol and sometimes it is hard to see how the different
requirements interact with each other.
diff --git a/examples/emulation/extserver/omnetpp.ini b/examples/emulation/extserver/omnetpp.ini
index dddd0d9c4b7..e5666bcf333 100644
--- a/examples/emulation/extserver/omnetpp.ini
+++ b/examples/emulation/extserver/omnetpp.ini
@@ -53,13 +53,13 @@ abstract = true
**.tcp.typename = "Tcp"
**.tcp.advertisedWindow = 65535 # in bytes, corresponds with the maximal receiver buffer capacity (Note: normally, NIC queues should be at least this size)
**.tcp.delayedAcksEnabled = false # delayed ACK algorithm (RFC 1122) enabled/disabled
-**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 896) enabled/disabled
+**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 1122) enabled/disabled
**.tcp.limitedTransmitEnabled = false # Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl)
**.tcp.increasedIWEnabled = false # Increased Initial Window (RFC 3390) enabled/disabled
-**.tcp.sackSupport = true # Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
-**.tcp.windowScalingSupport = false # Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
-**.tcp.timestampSupport = false # Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
-**.tcp.mss = 1452 # Maximum Segment Size (RFC 793) (header option)
+**.tcp.sackSupport = true # Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+**.tcp.windowScalingSupport = false # Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+**.tcp.timestampSupport = false # Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+**.tcp.mss = 1452 # Maximum Segment Size (RFC 9293) (header option)
**.tcp.tcpAlgorithmClass = "TcpReno" # TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl/DumbTcp
# pcapRecorder settings
diff --git a/examples/inet/ber/omnetpp.ini b/examples/inet/ber/omnetpp.ini
index 365e55788f4..58e363c79d0 100644
--- a/examples/inet/ber/omnetpp.ini
+++ b/examples/inet/ber/omnetpp.ini
@@ -49,13 +49,13 @@ abstract = true
**.tcp.typename = "Tcp"
**.tcp.advertisedWindow = 65535 # in bytes, corresponds with the maximal receiver buffer capacity (Note: normally, NIC queues should be at least this size)
**.tcp.delayedAcksEnabled = false # delayed ACK algorithm (RFC 1122) enabled/disabled
-**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 896) enabled/disabled
+**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 1122) enabled/disabled
**.tcp.limitedTransmitEnabled = false # Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl)
**.tcp.increasedIWEnabled = false # Increased Initial Window (RFC 3390) enabled/disabled
-**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
-**.tcp.windowScalingSupport = false # Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
-**.tcp.timestampSupport = false # Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
-**.tcp.mss = 1452 # Maximum Segment Size (RFC 793) (header option)
+**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+**.tcp.windowScalingSupport = false # Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+**.tcp.timestampSupport = false # Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+**.tcp.mss = 1452 # Maximum Segment Size (RFC 9293) (header option)
**.tcp.tcpAlgorithmClass = "TcpReno" # TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl/DumbTcp
# RNG/seed settings
diff --git a/examples/inet/dctcp/omnetpp.ini b/examples/inet/dctcp/omnetpp.ini
index 5d4c1960bcf..38b5fa9b75f 100644
--- a/examples/inet/dctcp/omnetpp.ini
+++ b/examples/inet/dctcp/omnetpp.ini
@@ -22,12 +22,12 @@ cmdenv-event-banners = true # for normal (non-express) mode only
**.tcp.delayedAcksEnabled = false # delayed ACK algorithm (RFC 1122) enabled/disabled
-**.tcp.nagleEnabled = false # Nagle's algorithm (RFC 896) enabled/disabled
+**.tcp.nagleEnabled = false # Nagle's algorithm (RFC 1122) enabled/disabled
**.tcp.limitedTransmitEnabled = false # Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TCPReno/TCPTahoe/TCPNewReno/TCPNoCongestionControl)
**.tcp.increasedIWEnabled = false # Increased Initial Window (RFC 3390) enabled/disabled
-**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
-**.tcp.windowScalingSupport = true # Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
-**.tcp.timestampSupport = false # Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+**.tcp.windowScalingSupport = true # Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+**.tcp.timestampSupport = false # Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
**.tcp.tcpAlgorithmClass = "DcTcp" # TCPReno/TCPTahoe/TCPNewReno/TCPNoCongestionControl/DumbTCP
**.tcp.advertisedWindow = 350000
**.tcp.mss = 1460
diff --git a/examples/inet/redmarker/omnetpp.ini b/examples/inet/redmarker/omnetpp.ini
index 037173a8f13..d6809dd3d6c 100644
--- a/examples/inet/redmarker/omnetpp.ini
+++ b/examples/inet/redmarker/omnetpp.ini
@@ -22,12 +22,12 @@ sim-time-limit = 100s
**.tcp.typename = "Tcp"
**.tcp.delayedAcksEnabled = false # delayed ACK algorithm (RFC 1122) enabled/disabled
-**.tcp.nagleEnabled = false # Nagle's algorithm (RFC 896) enabled/disabled
+**.tcp.nagleEnabled = false # Nagle's algorithm (RFC 1122) enabled/disabled
**.tcp.limitedTransmitEnabled = false # Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TCPReno/TCPTahoe/TCPNewReno/TCPNoCongestionControl)
**.tcp.increasedIWEnabled = false # Increased Initial Window (RFC 3390) enabled/disabled
-**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
-**.tcp.windowScalingSupport = true # Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
-**.tcp.timestampSupport = false # Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+**.tcp.windowScalingSupport = true # Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+**.tcp.timestampSupport = false # Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
**.tcp.tcpAlgorithmClass = "TcpReno" # TCPReno/TCPTahoe/TCPNewReno/TCPNoCongestionControl/DumbTCP
**.tcp.advertisedWindow = 5000000
**.tcp.mss = 1460
diff --git a/examples/inet/tcpwindowscale/omnetpp.ini b/examples/inet/tcpwindowscale/omnetpp.ini
index 2d32a7b3320..5a5c11a4efd 100644
--- a/examples/inet/tcpwindowscale/omnetpp.ini
+++ b/examples/inet/tcpwindowscale/omnetpp.ini
@@ -44,13 +44,13 @@ sim-time-limit = 600.0s
**.tcp.typename = "Tcp"
**.tcp.advertisedWindow = 65535 # in bytes, corresponds with the maximal receiver buffer capacity (Note: normally, NIC queues should be at least this size)
**.tcp.delayedAcksEnabled = false # delayed ACK algorithm (RFC 1122) enabled/disabled
-**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 896) enabled/disabled
+**.tcp.nagleEnabled = true # Nagle's algorithm (RFC 1122) enabled/disabled
**.tcp.limitedTransmitEnabled = false # Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl)
**.tcp.increasedIWEnabled = false # Increased Initial Window (RFC 3390) enabled/disabled
-**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
-**.tcp.windowScalingSupport = false # Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
-**.tcp.timestampSupport = false # Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
-**.tcp.mss = 1452 # Maximum Segment Size (RFC 793) (header option)
+**.tcp.sackSupport = false # Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+**.tcp.windowScalingSupport = false # Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+**.tcp.timestampSupport = false # Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+**.tcp.mss = 1452 # Maximum Segment Size (RFC 9293) (header option)
**.tcp.tcpAlgorithmClass = "TcpReno" # TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl/DumbTcp
#
diff --git a/src/inet/applications/tcpapp/TcpAppBase.cc b/src/inet/applications/tcpapp/TcpAppBase.cc
index 027424498da..6cc08b87b79 100644
--- a/src/inet/applications/tcpapp/TcpAppBase.cc
+++ b/src/inet/applications/tcpapp/TcpAppBase.cc
@@ -97,11 +97,11 @@ void TcpAppBase::close()
emit(connectSignal, -1L);
}
-void TcpAppBase::sendPacket(Packet *msg)
+void TcpAppBase::sendPacket(Packet *msg, bool eor)
{
int numBytes = msg->getByteLength();
emit(packetSentSignal, msg);
- socket.send(msg);
+ socket.send(msg, eor);
packetsSent++;
bytesSent += numBytes;
diff --git a/src/inet/applications/tcpapp/TcpAppBase.h b/src/inet/applications/tcpapp/TcpAppBase.h
index 213ae952b7f..ddbc850dd8d 100644
--- a/src/inet/applications/tcpapp/TcpAppBase.h
+++ b/src/inet/applications/tcpapp/TcpAppBase.h
@@ -45,7 +45,7 @@ class INET_API TcpAppBase : public ApplicationBase, public TcpSocket::ICallback
/* Utility functions */
virtual void connect();
virtual void close();
- virtual void sendPacket(Packet *pkt);
+ virtual void sendPacket(Packet *pkt, bool eor = false);
virtual void handleTimer(cMessage *msg) = 0;
diff --git a/src/inet/applications/tcpapp/TcpSessionApp.cc b/src/inet/applications/tcpapp/TcpSessionApp.cc
index 4df7141be6d..dbb9fefeef0 100644
--- a/src/inet/applications/tcpapp/TcpSessionApp.cc
+++ b/src/inet/applications/tcpapp/TcpSessionApp.cc
@@ -124,8 +124,9 @@ void TcpSessionApp::handleTimer(cMessage *msg)
void TcpSessionApp::sendData()
{
long numBytes = commands[commandIndex].numBytes;
- EV_INFO << "sending data with " << numBytes << " bytes\n";
- sendPacket(createDataPacket(numBytes));
+ bool eor = commands[commandIndex].eor;
+ EV_INFO << "sending data with " << numBytes << " bytes" << (eor ? " (eor)" : "") << "\n";
+ sendPacket(createDataPacket(numBytes), eor);
if (++commandIndex < (int)commands.size()) {
simtime_t tSend = commands[commandIndex].tSend;
@@ -148,7 +149,8 @@ Packet *TcpSessionApp::createDataPacket(long sendBytes)
const char *dataTransferMode = par("dataTransferMode");
Ptr payload;
if (!strcmp(dataTransferMode, "bytecount")) {
- payload = makeShared(B(sendBytes));
+ int packetData = par("packetData");
+ payload = packetData == -1 ? makeShared(B(sendBytes)) : makeShared(B(sendBytes), packetData);
}
else if (!strcmp(dataTransferMode, "object")) {
const auto& applicationPacket = makeShared();
@@ -159,8 +161,9 @@ Packet *TcpSessionApp::createDataPacket(long sendBytes)
const auto& bytesChunk = makeShared();
std::vector vec;
vec.resize(sendBytes);
+ int packetData = par("packetData");
for (int i = 0; i < sendBytes; i++)
- vec[i] = (bytesSent + i) & 0xFF;
+ vec[i] = packetData == -1 ? (bytesSent + i) & 0xFF : packetData;
bytesChunk->setBytes(vec);
payload = bytesChunk;
}
@@ -236,9 +239,20 @@ void TcpSessionApp::parseScript(const char *script)
while (isdigit(*s))
s++;
+ // MSG_EOR: optional "eor" keyword right after the byte
+ // count marks this command's SEND as a record boundary.
+ while (isspace(*s))
+ s++;
+
+ bool eor = false;
+ if (strncmp(s, "eor", 3) == 0 && !isalnum(s[3])) {
+ eor = true;
+ s += 3;
+ }
+
// add command
- EV_DEBUG << " add command (" << tSend << "s, " << numBytes << "B)\n";
- commands.push_back(Command(tSend, numBytes));
+ EV_DEBUG << " add command (" << tSend << "s, " << numBytes << "B" << (eor ? ", eor" : "") << ")\n";
+ commands.push_back(Command(tSend, numBytes, eor));
// skip delimiter
while (isspace(*s))
diff --git a/src/inet/applications/tcpapp/TcpSessionApp.h b/src/inet/applications/tcpapp/TcpSessionApp.h
index 9b9fa6b4fdf..d2b91e582e3 100644
--- a/src/inet/applications/tcpapp/TcpSessionApp.h
+++ b/src/inet/applications/tcpapp/TcpSessionApp.h
@@ -25,7 +25,8 @@ class INET_API TcpSessionApp : public TcpAppBase
struct Command {
simtime_t tSend;
long numBytes = 0;
- Command(simtime_t t, long n) { tSend = t; numBytes = n; }
+ bool eor = false; // MSG_EOR: "eor" keyword after the byte count in sendScript
+ Command(simtime_t t, long n, bool e = false) { tSend = t; numBytes = n; eor = e; }
};
typedef std::vector CommandVector;
CommandVector commands;
diff --git a/src/inet/applications/tcpapp/TcpSessionApp.ned b/src/inet/applications/tcpapp/TcpSessionApp.ned
index a7524dfc2b6..d96b81ec483 100644
--- a/src/inet/applications/tcpapp/TcpSessionApp.ned
+++ b/src/inet/applications/tcpapp/TcpSessionApp.ned
@@ -38,7 +38,10 @@ import inet.applications.contract.IApp;
// data. One way of specifying sending is via the `tSend`, `sendBytes`
// parameters, the other way is with `sendScript`. With the former, `sendBytes`
// bytes will be sent at `tSend`. With `sendScript`, the format is
-// " ; ;..."
+// " [ eor]; [ eor];...". The optional trailing
+// `eor` keyword marks that SEND's last byte as a record boundary (MSG_EOR):
+// TCP will not coalesce it together with data from a later SEND into the same
+// outgoing segment.
//
// Closing the connection
//
@@ -86,6 +89,7 @@ simple TcpSessionApp extends SimpleModule like IApp
string connectAddress;
int connectPort = default(1000);
string dataTransferMode @enum("bytecount","object","bytestream") = default("bytecount");
+ int packetData = default(-1); // the packet is filled with this byte if not -1
bool autoRead = default(true); // Whether to use "autoread" or "explicit-read" mode for TCP connection
volatile int readSize @unit(B) = default(-1B); // Used only with autoRead==false
volatile double readDelay @unit(s) = default(-1s); // Used only with autoRead==false; delay for issuing a READ command after previous READ was satisfied; -1 means immediately, 0 means zero delay
diff --git a/src/inet/common/packet/chunk/BitCountChunk.cc b/src/inet/common/packet/chunk/BitCountChunk.cc
index 897b578a6f8..0b2b9f81598 100644
--- a/src/inet/common/packet/chunk/BitCountChunk.cc
+++ b/src/inet/common/packet/chunk/BitCountChunk.cc
@@ -61,7 +61,7 @@ const Ptr BitCountChunk::peekUnchecked(PeekPredicate predicate, PeekConve
}
// 3. peeking without conversion returns a BitCountChunk
if (converter == nullptr) {
- auto result = makeShared(length < b(0) ? std::min(-length, chunkLength - iterator.getPosition()) : length);
+ auto result = makeShared(length < b(0) ? std::min(-length, chunkLength - iterator.getPosition()) : length, data);
result->regionTags.copyTags(regionTags, iterator.getPosition(), b(0), result->getChunkLength());
result->markImmutable();
return result;
diff --git a/src/inet/common/packet/chunk/ByteCountChunk.cc b/src/inet/common/packet/chunk/ByteCountChunk.cc
index ffa3ed6f102..3105d38514b 100644
--- a/src/inet/common/packet/chunk/ByteCountChunk.cc
+++ b/src/inet/common/packet/chunk/ByteCountChunk.cc
@@ -64,7 +64,7 @@ const Ptr ByteCountChunk::peekUnchecked(PeekPredicate predicate, PeekConv
if (converter == nullptr) {
// 3.a) peeking complete bytes without conversion returns a ByteCountChunk
if (iterator.getPosition().get() % 8 == 0 && (length < b(0) || length.get() % 8 == 0)) {
- auto chunk = makeShared(length < b(0) ? std::min(-length, chunkLength - iterator.getPosition()) : length);
+ auto chunk = makeShared(length < b(0) ? std::min(-length, chunkLength - iterator.getPosition()) : length, data);
chunk->regionTags.copyTags(regionTags, iterator.getPosition(), b(0), chunk->getChunkLength());
chunk->markImmutable();
return chunk;
diff --git a/src/inet/common/packet/recorder/PcapRecorder.cc b/src/inet/common/packet/recorder/PcapRecorder.cc
index bd3f673d703..1c4b2d62821 100644
--- a/src/inet/common/packet/recorder/PcapRecorder.cc
+++ b/src/inet/common/packet/recorder/PcapRecorder.cc
@@ -285,7 +285,7 @@ bool PcapRecorder::matchesLinkType(PcapLinkType pcapLinkType, const Protocol *pr
else if (*protocol == Protocol::ethernetMac)
return pcapLinkType == LINKTYPE_ETHERNET;
else if (*protocol == Protocol::ppp)
- return pcapLinkType == LINKTYPE_PPP_WITH_DIR;
+ return pcapLinkType == LINKTYPE_PPP;
else if (*protocol == Protocol::ieee80211Mac)
return pcapLinkType == LINKTYPE_IEEE802_11;
else if (*protocol == Protocol::ipv4)
@@ -310,7 +310,7 @@ PcapLinkType PcapRecorder::protocolToLinkType(const Protocol *protocol) const
else if (*protocol == Protocol::ethernetMac)
return LINKTYPE_ETHERNET;
else if (*protocol == Protocol::ppp)
- return LINKTYPE_PPP_WITH_DIR;
+ return LINKTYPE_PPP;
else if (*protocol == Protocol::ieee80211Mac)
return LINKTYPE_IEEE802_11;
else if (*protocol == Protocol::ipv4 || *protocol == Protocol::ipv6)
diff --git a/src/inet/linklayer/ppp/Ppp.cc b/src/inet/linklayer/ppp/Ppp.cc
index 4d1f9ec9d55..56cc2e08631 100644
--- a/src/inet/linklayer/ppp/Ppp.cc
+++ b/src/inet/linklayer/ppp/Ppp.cc
@@ -41,6 +41,7 @@ void Ppp::initialize(int stage)
if (stage == INITSTAGE_LOCAL) {
sendRawBytes = par("sendRawBytes");
endTransmissionEvent = new cMessage("pppEndTxEvent");
+ endTransmissionEvent->setSchedulingPriority(par("endTxSchedulingPriority"));
lowerLayerInGateId = findGate("phys$i");
physOutGate = gate("phys$o");
lowerLayerOutGateId = physOutGate->getId();
@@ -149,7 +150,12 @@ void Ppp::refreshOutGateConnection(bool connected)
simtime_t startTransmissionTime = endTransmissionEvent->getSendingTime();
simtime_t sentDuration = simTime() - startTransmissionTime;
double sentPart = sentDuration / (endTransmissionEvent->getArrivalTime() - startTransmissionTime);
- b newLength = b(floor(curTxPacket->getBitLength() * sentPart));
+ // Round the transmitted amount down to a whole byte: the partially sent
+ // packet is delivered with the bit-error flag set and then discarded, and
+ // INET's byte-granular chunks (e.g. ByteCountChunk) cannot represent a
+ // sub-byte length -- a non-byte-aligned truncation makes the packet
+ // unserializable (fails converting bits to bytes).
+ B newLength = B(floor(curTxPacket->getByteLength() * sentPart));
curTxPacket->removeAtBack(curTxPacket->getDataLength() - newLength);
curTxPacket->setBitError(true);
send(curTxPacket, SendOptions().finishTx(curTxPacket->getId()), physOutGate);
@@ -278,10 +284,6 @@ void Ppp::handleLowerPacket(Packet *packet)
else {
// pass up payload
const auto& pppHeader = packet->peekAtFront();
- const auto& pppTrailer = packet->peekAtBack(PPP_TRAILER_LENGTH);
- if (pppHeader == nullptr || pppTrailer == nullptr)
- throw cRuntimeError("Invalid PPP packet: PPP header or Trailer is missing");
- emit(receptionEndedSignal, packet);
emit(rxPkOkSignal, packet);
decapsulate(packet);
numRcvdOK++;
@@ -342,18 +344,12 @@ void Ppp::encapsulate(Packet *packet)
auto pppHeader = makeShared();
pppHeader->setProtocol(ProtocolGroup::getPppProtocolGroup()->getProtocolNumber(packet->getTag()->getProtocol()));
packet->insertAtFront(pppHeader);
- auto pppTrailer = makeShared();
- packet->insertAtBack(pppTrailer);
packet->addTagIfAbsent()->setProtocol(&Protocol::ppp);
}
void Ppp::decapsulate(Packet *packet)
{
const auto& pppHeader = packet->popAtFront();
- const auto& pppTrailer = packet->popAtBack(PPP_TRAILER_LENGTH);
- if (pppHeader == nullptr || pppTrailer == nullptr)
- throw cRuntimeError("Invalid PPP packet: PPP header or Trailer is missing");
- // TODO check FCS
packet->addTagIfAbsent()->setInterfaceId(networkInterface->getInterfaceId());
auto payloadProtocol = ProtocolGroup::getPppProtocolGroup()->getProtocol(pppHeader->getProtocol());
diff --git a/src/inet/linklayer/ppp/Ppp.ned b/src/inet/linklayer/ppp/Ppp.ned
index a6c66488f3c..d7f9066f1dc 100644
--- a/src/inet/linklayer/ppp/Ppp.ned
+++ b/src/inet/linklayer/ppp/Ppp.ned
@@ -44,6 +44,7 @@ simple Ppp extends SimpleModule
@lifecycleSupport;
double stopOperationExtraTime @unit(s) = default(-1s); // Extra time after lifecycle stop operation finished
double stopOperationTimeout @unit(s) = default(2s); // Timeout value for lifecycle stop operation
+ int endTxSchedulingPriority = default(0);
@class(Ppp);
@display("i=block/rxtx");
diff --git a/src/inet/linklayer/ppp/PppFrame.msg b/src/inet/linklayer/ppp/PppFrame.msg
index ead00f06cd1..3b2fbe58974 100644
--- a/src/inet/linklayer/ppp/PppFrame.msg
+++ b/src/inet/linklayer/ppp/PppFrame.msg
@@ -4,42 +4,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
//
-//
-// PPP header+trailer length:
-// - Flag(8) + Address(8) + Control(8) + Protocol(16) + FCS(16) = 7 octets.
-// (The terminating Flag is not counted, as rfc1331 states that
-// only one Flag is required between back-to-back frames.)
-//
-
import inet.common.INETDefs;
import inet.common.packet.chunk.Chunk;
namespace inet;
cplusplus {{
-const B PPP_HEADER_LENGTH = B(5);
-const B PPP_TRAILER_LENGTH = B(2);
+const B PPP_HEADER_LENGTH = B(2);
}}
//
-// PPP frame.
-//
-// Constant-value header fields are not modelled:
-// Flag (=0x7e), address (=0xff), control (=0x03), FCS (bitError())
+// PPP frame format according to RFC 1661.
//
-// FCS is modelled only via cMessage's bit error attribute.
class PppHeader extends FieldsChunk
{
chunkLength = PPP_HEADER_LENGTH;
- short flag = 0x7e; //1 byte
- short address = 0xff; //1 byte
- short control = 0x03; //1 byte
- int protocol = -1; //2 byte
-}
-
-class PppTrailer extends FieldsChunk
-{
- chunkLength = PPP_TRAILER_LENGTH; //FIXME correct value is 3, but old inet was used 2 bytes
- short fcs = 0; // frame check sequence, 2 byte
- short flag = 0x7e; // 1 byte, omitted for successive PPP packets
+ int protocol = -1; // 2 bytes
}
diff --git a/src/inet/linklayer/ppp/PppHeaderSerializer.cc b/src/inet/linklayer/ppp/PppHeaderSerializer.cc
index f55c0f803e1..3b911896abf 100644
--- a/src/inet/linklayer/ppp/PppHeaderSerializer.cc
+++ b/src/inet/linklayer/ppp/PppHeaderSerializer.cc
@@ -13,41 +13,19 @@
namespace inet {
Register_Serializer(PppHeader, PppHeaderSerializer);
-Register_Serializer(PppTrailer, PppTrailerSerializer);
void PppHeaderSerializer::serialize(MemoryOutputStream& stream, const Ptr& chunk) const
{
const auto& pppHeader = staticPtrCast(chunk);
- stream.writeUint8(pppHeader->getFlag());
- stream.writeUint8(pppHeader->getAddress());
- stream.writeUint8(pppHeader->getControl());
stream.writeUint16Be(pppHeader->getProtocol());
}
const Ptr PppHeaderSerializer::deserialize(MemoryInputStream& stream) const
{
auto pppHeader = makeShared();
- pppHeader->setFlag(stream.readUint8());
- pppHeader->setAddress(stream.readUint8());
- pppHeader->setControl(stream.readUint8());
pppHeader->setProtocol(stream.readUint16Be());
return pppHeader;
}
-void PppTrailerSerializer::serialize(MemoryOutputStream& stream, const Ptr& chunk) const
-{
- const auto& pppTrailer = staticPtrCast(chunk);
- stream.writeUint16Be(pppTrailer->getFcs());
-// stream.writeUint8(pppTrailer->getFlag()); //KLUDGE length is currently 2 bytes instead of 3 bytes
-}
-
-const Ptr PppTrailerSerializer::deserialize(MemoryInputStream& stream) const
-{
- auto pppTrailer = makeShared();
- pppTrailer->setFcs(stream.readUint16Be());
-// pppTrailer->setFlag(stream.readUint8()); //KLUDGE length is currently 2 bytes instead of 3 bytes
- return pppTrailer;
-}
-
} // namespace inet
diff --git a/src/inet/linklayer/ppp/PppHeaderSerializer.h b/src/inet/linklayer/ppp/PppHeaderSerializer.h
index 5e9ce685f1c..4788cd4b651 100644
--- a/src/inet/linklayer/ppp/PppHeaderSerializer.h
+++ b/src/inet/linklayer/ppp/PppHeaderSerializer.h
@@ -25,19 +25,6 @@ class INET_API PppHeaderSerializer : public FieldsChunkSerializer
PppHeaderSerializer() : FieldsChunkSerializer() {}
};
-/**
- * Converts between PppTrailer and binary (network byte order) Ppp trailer.
- */
-class INET_API PppTrailerSerializer : public FieldsChunkSerializer
-{
- protected:
- virtual void serialize(MemoryOutputStream& stream, const Ptr& chunk) const override;
- virtual const Ptr deserialize(MemoryInputStream& stream) const override;
-
- public:
- PppTrailerSerializer() : FieldsChunkSerializer() {}
-};
-
} // namespace inet
#endif
diff --git a/src/inet/linklayer/ppp/PppProtocolDissector.cc b/src/inet/linklayer/ppp/PppProtocolDissector.cc
index 5dc39c23194..c7b2e710515 100644
--- a/src/inet/linklayer/ppp/PppProtocolDissector.cc
+++ b/src/inet/linklayer/ppp/PppProtocolDissector.cc
@@ -20,11 +20,9 @@ void PppProtocolDissector::dissect(Packet *packet, const Protocol *protocol, ICa
{
callback.startProtocolDataUnit(&Protocol::ppp);
const auto& header = packet->popAtFront();
- const auto& trailer = packet->popAtBack(PPP_TRAILER_LENGTH);
callback.visitChunk(header, &Protocol::ppp);
auto payloadProtocol = ProtocolGroup::getPppProtocolGroup()->findProtocol(header->getProtocol());
callback.dissectPacket(packet, payloadProtocol);
- callback.visitChunk(trailer, &Protocol::ppp);
callback.endProtocolDataUnit(&Protocol::ppp);
}
diff --git a/src/inet/transportlayer/contract/tcp/TcpCommand.msg b/src/inet/transportlayer/contract/tcp/TcpCommand.msg
index 69cb4b56ac0..95e41a1597d 100644
--- a/src/inet/transportlayer/contract/tcp/TcpCommand.msg
+++ b/src/inet/transportlayer/contract/tcp/TcpCommand.msg
@@ -52,6 +52,7 @@ enum TcpStatusInd
TCP_I_SEND_MSG = 11; // send queue abated, send more messages
TCP_I_ICMPv4_ERROR = 12; // ICMPv4 error received (carries ~Icmpv4ErrorInd tag)
TCP_I_ICMPv6_ERROR = 13; // ICMPv6 error received (carries ~Icmpv6ErrorInd tag)
+ TCP_I_ZEROCOPY_COMPLETION = 14; // a zerocopy-marked SEND's data has been transmitted (MSG_ZEROCOPY; carries ~TcpZerocopyCompletionInfo)
}
//
@@ -78,6 +79,10 @@ enum TcpErrorCode
class TcpCommand extends cObject
{
int userId = -1; // id than can be freely used by the app
+ bool halfClose = false; // only meaningful on TCP_C_CLOSE: true = shutdown(SHUT_WR) semantics
+ // (send FIN but the application keeps reading); false = full close()
+ // (Linux sk_shutdown gets RCV_SHUTDOWN too: new data arriving after a
+ // full close resets the connection, RFC 1122 4.2.2.13)
}
//
@@ -122,6 +127,7 @@ class TcpOpenCommand extends TcpCommand
bool fork = false; // used only for passive open
bool autoRead = true; // true: TCPs sends up arrived data automatically. false: should use read command
string tcpAlgorithmClass; // TCP congestion control algorithm; leave empty for default
+ bool fastOpen = false; // active open only: attempt TCP Fast Open (RFC 7413) -- attach data to the SYN if a cookie is already cached for remoteAddr, else request one
}
//
@@ -173,6 +179,126 @@ class TcpSetTosCommand extends TcpSetOptionCommand
short tos; // type of service for Ipv4 / traffic class for Ipv6
}
+//
+// SO_TIMESTAMPING/SCM_TIMESTAMPING: enables/disables attaching a
+// TcpRxTimestampInd tag (see TcpTimestampingTag.msg) to every TCP_I_DATA packet
+// delivered to the app on this connection.
+//
+// @see ~TcpSocket::setTimestamping
+//
+class TcpSetTimestampingCommand extends TcpSetOptionCommand
+{
+ bool enabled = true;
+}
+
+//
+// TCP_NOTSENT_LOWAT as a runtime socket option: sets
+// the connection's not-yet-transmitted-bytes low-water mark at runtime, same
+// semantics as the notsentLowat module parameter (which supplies the initial
+// value at connection setup). -1 disables.
+//
+// @see ~TcpSocket::setNotsentLowat
+//
+class TcpSetNotsentLowatCommand extends TcpSetOptionCommand
+{
+ int value = -1;
+}
+
+//
+// TCP_MAXSEG (setsockopt SOL_TCP): the application clamps the Maximum Segment
+// Size for this connection. Like Linux's rx_opt.user_mss, it caps both the MSS
+// this host advertises in its SYN/SYN-ACK and the effective sending MSS. -1
+// (default) means "not set". May be sent before connect()/listen().
+//
+class TcpSetMaxSegCommand extends TcpSetOptionCommand
+{
+ int value = -1;
+}
+
+//
+// The route's MTU changed under an open connection (Linux: a new dst_mtu, which
+// tcp_current_mss picks up as icsk_pmtu_cookie). It raises the ceiling the RFC 4821
+// MTU search may work up to; it does not by itself change the MSS in use, which only
+// moves once a probe of the larger size has been acknowledged.
+//
+// @see ~TcpSocket::setPathMtu
+//
+class TcpSetPathMtuCommand extends TcpSetOptionCommand
+{
+ int value = 0;
+}
+
+//
+// SO_RCVBUF as a runtime socket option: sets this connection's receive-BUFFER
+// capacity (Linux sk_rcvbuf) and pins it, so the kernel's automatic growth under
+// receive pressure no longer applies (SOCK_RCVBUF_LOCK). `value` is the final
+// buffer size -- the socket layer has already applied Linux's doubling of the
+// setsockopt argument. Shrinking it below what is already queued is legal and is
+// how an application forces a memory squeeze: nothing is discarded, but the next
+// arrival has nowhere to go.
+//
+// @see ~TcpSocket::setReceiveBufferSize
+//
+class TcpSetRcvBufCommand extends TcpSetOptionCommand
+{
+ int value = -1;
+}
+
+//
+// TCP_NODELAY (setsockopt SOL_TCP): enable/disable Nagle's algorithm at runtime.
+// nodelay==true disables Nagle and force-pushes any held partial segment (but does
+// NOT clear TCP_CORK, which outranks TCP_NODELAY in Linux).
+//
+// Marks whether the connection is OWNED by an application socket (Linux
+// sk->sk_socket): a listening-side connection is embryonic until the
+// application accept()s it, and several kernel behaviors are gated on
+// ownership -- e.g. tcp_data_queue_ofo's "do not grow rcvbuf for
+// not-yet-accepted or orphaned sockets". Default is owned (active opens,
+// forked-and-accepted connections); a harness driving accept() timing
+// explicitly clears it at listen and sets it at the script's accept().
+class TcpSetOwnedCommand extends TcpSetOptionCommand
+{
+ bool owned = true;
+}
+
+// The application writer's blocked-on-send-buffer state (Linux SOCK_NOSPACE
+// while a blocking write waits in sk_stream_wait_memory): while set AND the
+// send queue has run dry, the connection accumulates SNDBUF_LIMITED chrono
+// time (tcpi_sndbuf_limited). Driven by the application layer, which is where
+// Linux's signal originates too.
+class TcpSetWriterBlockedCommand extends TcpSetOptionCommand
+{
+ bool blocked = false;
+}
+
+class TcpSetNoDelayCommand extends TcpSetOptionCommand
+{
+ bool nodelay = true;
+}
+
+//
+// TCP_CORK (setsockopt SOL_TCP): hold sub-MSS partial segments (full segments still
+// flow) until the cork is cleared, an incoming ACK opens the window, or the cork
+// (RTO) timer fires. Clearing the cork (cork==false) flushes any held partial.
+//
+class TcpSetCorkCommand extends TcpSetOptionCommand
+{
+ bool cork = true;
+}
+
+//
+// Sent with message kind TCP_I_ZEROCOPY_COMPLETION, in response to a
+// zerocopy-marked SEND (MSG_ZEROCOPY -- see
+// ~TcpSendZerocopyReq/~TcpSocket::sendZerocopy) once that SEND's data has
+// been transmitted.
+//
+// @see ~TcpStatusInd, ~TcpCommandCode, ~ITcp
+//
+class TcpZerocopyCompletionInfo extends TcpCommand
+{
+ unsigned int zerocopyId;
+}
+
//
// Sent with message kind TCP_I_AVAILABLE, to let the app know
// about the local and remote IP address and port.
@@ -206,7 +332,7 @@ class TcpConnectInfo extends TcpCommand
//
// Sent with message kind TCP_I_STATUS, in response to command TCP_C_STATUS.
-// For explanation of variables, see RFC 793 or TcpStateVariables in
+// For explanation of variables, see RFC 9293 or TcpStateVariables in
// TcpConnection.h.
//
// @see ~TcpStatusInd, ~TcpCommandCode, ~ITcp
@@ -223,6 +349,8 @@ class TcpStatusInfo extends TcpCommand
bool autoRead; // true: TCP send up arrived data automatically. false: should use read command.
unsigned int snd_mss;
+ unsigned int sndEffMss; // effective send MSS after header options (Linux tcp_current_mss: e.g. 1448 with timestamps on a 1460 path) -- tcpi_snd_mss reports THIS
+ unsigned int advmss; // the MSS this host advertised in its SYN/SYN-ACK (state->advertisedMss); Linux tcp_info tcpi_advmss
unsigned int snd_una;
unsigned int snd_nxt;
@@ -239,4 +367,78 @@ class TcpStatusInfo extends TcpCommand
unsigned int irs;
bool fin_ack_rcvd;
+
+ // Additional fields below bridge a Linux struct tcp_info-style status query
+ // (e.g. for packetdrill's %{ }% assertion blocks) -- not part of RFC 793,
+ // sourced from the congestion-control/loss-recovery state variables where
+ // available. Fields with no equivalent for the connection's flavour (e.g.
+ // DumbTcp has no congestion window) are set to UINT_MAX/-1 as a sentinel;
+ // callers must check for that before using the value.
+ unsigned int cwnd;
+ unsigned int ssthresh;
+ unsigned int reordering;
+ double srtt;
+ double minRtt;
+ unsigned int flightSize;
+ unsigned int sackedBytes;
+ unsigned int deliveredBytes;
+ unsigned int rexmitCount;
+ unsigned int numRtos;
+ bool tsEnabled;
+ bool sackEnabled;
+ bool wsEnabled;
+ bool ectEnabled;
+ bool synDataAccepted; // server accepted TFO SYN data ahead of the 3WHS (-> TCPI_OPT_SYN_DATA)
+ unsigned int sndWndScale;
+ simtime_t lastDataRecvTime;
+
+ // Linux tcp_ca_state ordinal (Open=0/Disorder=1/CWR=2/Recovery=3/Loss=4), derived
+ // from INET's independent loss-recovery bools -- see
+ // TcpConnection::deriveLinuxCaState(). INET never tracks Disorder(1) as a distinct
+ // state; see that function's doc comment.
+ int caState;
+ // Backoff/retransmit counter: same underlying counter as rexmitCount above (INET
+ // does not track Linux's tcpi_backoff and tcpi_retransmits as separate concepts).
+ unsigned int backoff;
+ // SACK/RACK-tracked lost bytes (TcpSackRexmitQueue::getTotalAmountOfLostBytes()),
+ // converted to an approximate segment count via integer division by snd_mss -- a
+ // lossy but honest approximation of Linux's tcpi_lost, which counts segments
+ // directly. UINT_MAX if SACK is not enabled on this connection (no loss tracking).
+ unsigned int lost;
+ // Retransmitted-and-still-outstanding segments (TcpSackRexmitQueue::getRetrans()),
+ // same segment approximation and UINT_MAX sentinel as `lost` (Linux tcpi_retrans /
+ // retrans_out).
+ unsigned int retrans;
+ // Linux SK_MEMINFO_RCVBUF (sk->sk_rcvbuf): the live receive-buffer
+ // capacity -- receiveBufferSize as configured, possibly GROWN by
+ // tcp_clamp_window under out-of-order buffer pressure on an
+ // application-owned socket. 0 when no buffer size is configured.
+ unsigned int skRcvbuf;
+ // Cumulative zero-window (persist) probes sent (TcpBaseAlgStateVariables::
+ // zeroWindowProbesSent). UINT_MAX for flavours without persist-probe tracking.
+ unsigned int probes;
+ // Total bytes received in-sequence so far (rcv_nxt - irs - 1).
+ unsigned int bytesReceived;
+ // AccECN sender-side resolved CE-marked packet/byte counts
+ // (state->deliveredCePkts/deliveredCeBytes); 0 (not a sentinel) when AccECN was
+ // never negotiated on this connection, matching Linux's own tcp_info behavior for
+ // an ECN-less connection.
+ unsigned int deliveredCePkts;
+ unsigned int deliveredCeBytes;
+ // AccECN peer-reported ECT0/ECT1 delivered-byte counts (state->deliveredE0Bytes/E1Bytes),
+ // reported as tcpi_delivered_e0_bytes / tcpi_delivered_e1_bytes.
+ unsigned int deliveredE0Bytes;
+ unsigned int deliveredE1Bytes;
+
+ // Cumulative time (seconds) the connection was busy sending data, and time
+ // specifically blocked by the peer's advertised window rather than the
+ // congestion window -- see TcpConnection::enqueueSendCommandData()/
+ // processAckInEstabEtc()/sendData() (INET-native bookkeeping, not a port of
+ // Linux's tcpi_busy_time/tcpi_rwnd_limited, which are cumulative microseconds
+ // from a kernel-internal chrono state machine this reimplements independently).
+ // sndbuf_limited has no INET analog and is not surfaced (INET's send queue is
+ // never actually blocking -- see notsentLowat/sendQueueLimit above).
+ double busyTime;
+ double rwndLimited;
+ double sndbufLimited; // Linux tcpi_sndbuf_limited (TCP_CHRONO_SNDBUF_LIMITED), seconds -- see sndbufLimitedAccumulated
}
diff --git a/src/inet/transportlayer/contract/tcp/TcpSendEorTag.msg b/src/inet/transportlayer/contract/tcp/TcpSendEorTag.msg
new file mode 100644
index 00000000000..8ca6907dc51
--- /dev/null
+++ b/src/inet/transportlayer/contract/tcp/TcpSendEorTag.msg
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.TagBase;
+
+namespace inet;
+
+//
+// Request tag an application attaches to a TCP_C_SEND data packet (MSG_EOR)
+// to mark the packet's last byte as a record boundary: TCP will
+// not build a segment spanning it, i.e. it will never coalesce this SEND's
+// data together with a later SEND's data into the same outgoing segment. A
+// SEND packet with no TcpSendEorReq tag imposes no boundary.
+//
+// @see ~TcpSocket::send
+//
+class TcpSendEorReq extends TagBase
+{
+}
diff --git a/src/inet/transportlayer/contract/tcp/TcpSendMoreTag.msg b/src/inet/transportlayer/contract/tcp/TcpSendMoreTag.msg
new file mode 100644
index 00000000000..c8826b4e204
--- /dev/null
+++ b/src/inet/transportlayer/contract/tcp/TcpSendMoreTag.msg
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.TagBase;
+
+namespace inet;
+
+//
+// Request tag an application attaches to a TCP_C_SEND data packet (MSG_MORE) to
+// indicate more data is coming: TCP holds this send's trailing sub-MSS partial
+// segment (like TCP_CORK, but only for this one send) instead of transmitting it
+// immediately. The held partial is flushed by a later send without MSG_MORE, an
+// incoming ACK that opens the window, an explicit uncork/nodelay, or the cork
+// (RTO/probe) timer. A SEND packet with no TcpSendMoreReq tag holds nothing back.
+//
+// @see ~TcpSocket::send
+//
+class TcpSendMoreReq extends TagBase
+{
+}
diff --git a/src/inet/transportlayer/contract/tcp/TcpSocket.cc b/src/inet/transportlayer/contract/tcp/TcpSocket.cc
index 12a771f9742..b2252e005ea 100644
--- a/src/inet/transportlayer/contract/tcp/TcpSocket.cc
+++ b/src/inet/transportlayer/contract/tcp/TcpSocket.cc
@@ -124,6 +124,11 @@ void TcpSocket::accept(int socketId)
}
void TcpSocket::connect(L3Address remoteAddress, int remotePort)
+{
+ connect(remoteAddress, remotePort, false);
+}
+
+void TcpSocket::connect(L3Address remoteAddress, int remotePort, bool fastOpen)
{
if (sockstate != NOT_BOUND && sockstate != BOUND)
throw cRuntimeError("TcpSocket::connect(): connect() or listen() already called (need renewSocket()?)");
@@ -143,6 +148,7 @@ void TcpSocket::connect(L3Address remoteAddress, int remotePort)
openCmd->setRemotePort(remotePrt);
openCmd->setAutoRead(autoRead);
openCmd->setTcpAlgorithmClass(tcpAlgorithmClass.c_str());
+ openCmd->setFastOpen(fastOpen);
request->setControlInfo(openCmd);
sendToTcp(request);
@@ -174,6 +180,20 @@ void TcpSocket::send(Packet *msg)
sendToTcp(msg);
}
+void TcpSocket::send(Packet *msg, bool eor)
+{
+ if (eor)
+ msg->addTagIfAbsent();
+
+ send(msg);
+}
+
+void TcpSocket::sendZerocopy(Packet *msg)
+{
+ msg->addTagIfAbsent();
+ send(msg);
+}
+
void TcpSocket::sendCommand(Request *msg)
{
sendToTcp(msg);
@@ -252,6 +272,87 @@ void TcpSocket::setTos(short dscp)
sendToTcp(request);
}
+void TcpSocket::setTimestamping(bool enabled)
+{
+ auto request = new Request("setTimestamping", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetTimestampingCommand();
+ cmd->setEnabled(enabled);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setNotsentLowat(int value)
+{
+ auto request = new Request("setNotsentLowat", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetNotsentLowatCommand();
+ cmd->setValue(value);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setMaxSeg(int value)
+{
+ auto request = new Request("setMaxSeg", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetMaxSegCommand();
+ cmd->setValue(value);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setPathMtu(int value)
+{
+ auto request = new Request("setPathMtu", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetPathMtuCommand();
+ cmd->setValue(value);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setReceiveBufferSize(int value)
+{
+ auto request = new Request("setReceiveBufferSize", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetRcvBufCommand();
+ cmd->setValue(value);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setNoDelay(bool nodelay)
+{
+ auto request = new Request("setNoDelay", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetNoDelayCommand();
+ cmd->setNodelay(nodelay);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setOwned(bool owned)
+{
+ auto request = new Request("setOwned", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetOwnedCommand();
+ cmd->setOwned(owned);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setWriterBlocked(bool blocked)
+{
+ auto request = new Request("setWriterBlocked", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetWriterBlockedCommand();
+ cmd->setBlocked(blocked);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
+void TcpSocket::setCork(bool cork)
+{
+ auto request = new Request("setCork", TCP_C_SETOPTION);
+ auto *cmd = new TcpSetCorkCommand();
+ cmd->setCork(cork);
+ request->setControlInfo(cmd);
+ sendToTcp(request);
+}
+
// ######################
// TCP Socket Options End
// ######################
@@ -395,6 +496,22 @@ void TcpSocket::processMessage(cMessage *msg)
delete msg;
break;
+ case TCP_I_SEND_MSG: {
+ auto *tcpCommand = check_and_cast(msg->getControlInfo());
+ if (cb)
+ cb->socketSendMsgArrived(this, tcpCommand);
+ delete msg;
+ break;
+ }
+
+ case TCP_I_ZEROCOPY_COMPLETION: {
+ auto *completionInfo = check_and_cast(msg->getControlInfo());
+ if (cb)
+ cb->socketZerocopyCompletion(this, completionInfo->getZerocopyId());
+ delete msg;
+ break;
+ }
+
default:
throw cRuntimeError("TcpSocket: invalid msg kind %d, one of the TCP_I_xxx constants expected", msg->getKind());
}
diff --git a/src/inet/transportlayer/contract/tcp/TcpSocket.h b/src/inet/transportlayer/contract/tcp/TcpSocket.h
index cea52099ff2..0a6b1567f16 100644
--- a/src/inet/transportlayer/contract/tcp/TcpSocket.h
+++ b/src/inet/transportlayer/contract/tcp/TcpSocket.h
@@ -17,6 +17,8 @@
#include "inet/networklayer/common/Icmpv6ErrorTag_m.h"
#include "inet/networklayer/common/L3Address.h"
#include "inet/transportlayer/contract/tcp/TcpCommand_m.h"
+#include "inet/transportlayer/contract/tcp/TcpSendEorTag_m.h"
+#include "inet/transportlayer/contract/tcp/TcpZerocopyTag_m.h"
namespace inet {
@@ -165,6 +167,22 @@ class INET_API TcpSocket : public ISocket
* Default implementation does nothing (backward compatible).
*/
virtual void socketIcmpv6Error(TcpSocket *socket, Indication *errorInd) {}
+
+ /**
+ * Notifies that TCP's send queue has abated below the configured
+ * low-water mark (sendQueueLimit via TCP_C_QUEUE_BYTES_LIMIT, or
+ * notsentLowat via the module parameter of the same name -- see
+ * requestStatus()'s sibling mechanisms) and is ready to accept more
+ * data. Default implementation does nothing (backward compatible).
+ */
+ virtual void socketSendMsgArrived(TcpSocket *socket, TcpCommand *tcpCommand) {}
+
+ /**
+ * Notifies that a zerocopy-marked SEND's data has been transmitted
+ * (MSG_ZEROCOPY -- see TcpSocket::sendZerocopy()).
+ * Default implementation does nothing (backward compatible).
+ */
+ virtual void socketZerocopyCompletion(TcpSocket *socket, unsigned int zerocopyId) {}
};
/**
@@ -358,6 +376,14 @@ class INET_API TcpSocket : public ISocket
*/
void connect(L3Address remoteAddr, int remotePort);
+ /**
+ * Active OPEN to the given remote socket, with TCP Fast Open (RFC 7413)
+ * requested: if a cookie is already cached for remoteAddr, the SYN attempts
+ * to carry the first SEND's data; otherwise the SYN just requests a cookie
+ * for a future attempt.
+ */
+ void connect(L3Address remoteAddr, int remotePort, bool fastOpen);
+
/**
* This function is only in use in "explicit-read" mode, i.e. when the
* autoRead is turned off with setAutoRead(false).
@@ -375,6 +401,26 @@ class INET_API TcpSocket : public ISocket
*/
virtual void send(Packet *msg) override;
+ /**
+ * Sends data packet, marking its last byte as a record boundary (like the
+ * MSG_EOR sendto() flag): TCP will not coalesce this SEND's data together
+ * with a later SEND's data into the same outgoing segment.
+ */
+ void send(Packet *msg, bool eor);
+
+ /**
+ * Sends data packet, requesting a zerocopy-style completion notification
+ * (MSG_ZEROCOPY) once this SEND's data has been
+ * transmitted: the callback's socketZerocopyCompletion() (see ICallback)
+ * fires with the id assigned to this SEND -- ids are assigned
+ * sequentially per connection starting at 0, mirroring Linux's own
+ * SO_ZEROCOPY id assignment, so the caller can predict the id of its own
+ * Nth zerocopy SEND without needing it echoed back synchronously. INET
+ * has no real zero-copy data path; this models only the completion-
+ * notification contract (see TcpZerocopyTag.msg).
+ */
+ void sendZerocopy(Packet *msg);
+
/**
* Sends command.
*/
@@ -424,6 +470,69 @@ class INET_API TcpSocket : public ISocket
*/
void setTos(short tos);
+ /**
+ * Enables or disables delivery-time timestamping
+ * (SO_TIMESTAMPING/SCM_TIMESTAMPING): when enabled, every TCP_I_DATA packet
+ * this socket receives carries a TcpRxTimestampInd tag recording when TCP
+ * delivered it (see TcpTimestampingTag.msg for the exact semantics --
+ * an INET-native simplification, not a full port of Linux's timestamp
+ * triad).
+ */
+ void setTimestamping(bool enabled);
+
+ /**
+ * Sets the TCP_NOTSENT_LOWAT write-readiness low-water mark (in bytes) at
+ * runtime — same semantics as the Tcp module's notsentLowat parameter,
+ * which supplies the initial value. -1 disables.
+ */
+ void setNotsentLowat(int value);
+
+ /**
+ * TCP_MAXSEG (setsockopt SOL_TCP): clamp the connection's MSS -- both the MSS
+ * advertised in this host's SYN/SYN-ACK and the effective sending MSS.
+ */
+ void setMaxSeg(int value);
+
+ /**
+ * The route's MTU under this connection changed: raises the ceiling RFC 4821
+ * MTU probing may search up to. The MSS in use is unaffected until a probe of
+ * the larger size is acknowledged.
+ */
+ void setPathMtu(int value);
+
+ /**
+ * SO_RCVBUF: pins the connection's receive-buffer capacity (Linux sk_rcvbuf,
+ * already doubled by the caller). A pinned buffer no longer grows under receive
+ * pressure, so shrinking it below the queued data makes the next arrival be
+ * dropped and a zero window advertised.
+ */
+ void setReceiveBufferSize(int value);
+
+ /**
+ * TCP_NODELAY (setsockopt SOL_TCP): enable/disable Nagle at runtime. Enabling
+ * nodelay also force-pushes any held partial segment (but does not clear TCP_CORK).
+ */
+ void setNoDelay(bool nodelay);
+
+ /**
+ * Application-ownership marker (Linux sk->sk_socket): a harness driving
+ * accept() timing explicitly clears it at listen and sets it at accept;
+ * kernel behaviors like OOO-pressure rcvbuf growth are gated on it.
+ */
+ void setOwned(bool owned);
+
+ /**
+ * Application-writer blocked-on-send-buffer marker (Linux SOCK_NOSPACE
+ * during a blocking write): drives the tcpi_sndbuf_limited chrono.
+ */
+ void setWriterBlocked(bool blocked);
+
+ /**
+ * TCP_CORK (setsockopt SOL_TCP): hold sub-MSS partial segments until cleared
+ * (full segments still flow). Clearing the cork flushes any held partial.
+ */
+ void setCork(bool cork);
+
/**
* Required to re-connect with a "used" TcpSocket object.
* By default, a TcpSocket object is tied to a single TCP connection,
diff --git a/src/inet/transportlayer/contract/tcp/TcpTimestampingTag.msg b/src/inet/transportlayer/contract/tcp/TcpTimestampingTag.msg
new file mode 100644
index 00000000000..600af6a5190
--- /dev/null
+++ b/src/inet/transportlayer/contract/tcp/TcpTimestampingTag.msg
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.TagBase;
+
+namespace inet;
+
+//
+// Indication tag TCP attaches to a TCP_I_DATA packet
+// (SO_TIMESTAMPING/SCM_TIMESTAMPING) when the connection has timestamping
+// enabled (see ~TcpSocket::setTimestamping), recording when TCP delivered
+// this data to the application (simTime() at the point of delivery in
+// sendAvailableDataToApp()/process_READ_REQUEST()).
+//
+// This is an INET-native simplification of Linux's SCM_TIMESTAMPING: a
+// single delivery-time stamp, not Linux's separate software/hardware/ACK
+// timestamp triad, and not a reconstruction of each byte's original
+// segment-arrival time across out-of-order reassembly -- for in-order,
+// autoRead delivery (the common case) delivery time is effectively
+// simultaneous with segment processing, making it a faithful proxy for
+// "when received".
+//
+class TcpRxTimestampInd extends TagBase
+{
+ simtime_t timestamp = simTime(); // stamped at construction time, at the delivery call site
+}
diff --git a/src/inet/transportlayer/contract/tcp/TcpZerocopyTag.msg b/src/inet/transportlayer/contract/tcp/TcpZerocopyTag.msg
new file mode 100644
index 00000000000..91fc1045b46
--- /dev/null
+++ b/src/inet/transportlayer/contract/tcp/TcpZerocopyTag.msg
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.TagBase;
+
+namespace inet;
+
+//
+// Request tag an application attaches to a TCP_C_SEND data packet
+// (MSG_ZEROCOPY) to ask for a completion notification once this SEND's
+// data has actually been transmitted. TCP assigns IDs itself, sequentially
+// per connection starting at 0 for the first zerocopy-marked SEND -- mirroring
+// Linux's own SO_ZEROCOPY id assignment (see setsockopt(2)/SO_EE_ORIGIN_
+// ZEROCOPY), so the app can predict the id of its own Nth zerocopy SEND
+// without TCP needing to echo it back synchronously.
+//
+// INET has no real zero-copy data path (packet payloads are always plain C++
+// objects, not user-space memory pages TCP could avoid copying) -- this
+// models only the completion-notification CONTRACT Linux's MSG_ZEROCOPY
+// exposes to the app (see ~TcpZerocopyCompletionInfo, TCP_I_ZEROCOPY_
+// COMPLETION), not a literal zero-copy send path. "Transmitted" here means
+// TCP has put the corresponding bytes on the wire -- not that the peer has
+// acknowledged them, matching Linux's own timing (the kernel signals
+// completion once it is done reading the user buffer, not once the data is
+// acked).
+//
+// @see ~TcpSocket::sendZerocopy
+//
+class TcpSendZerocopyReq extends TagBase
+{
+}
diff --git a/src/inet/transportlayer/sctp/SctpAlg.cc b/src/inet/transportlayer/sctp/SctpAlg.cc
index eb701d9f6d4..1358002905d 100644
--- a/src/inet/transportlayer/sctp/SctpAlg.cc
+++ b/src/inet/transportlayer/sctp/SctpAlg.cc
@@ -68,7 +68,7 @@ void SctpAlg::receivedDuplicateAck()
EV_INFO << "Duplicate ACK #" << endl;
}
-void SctpAlg::receivedAckForDataNotYetSent(uint32_t seq)
+void SctpAlg::receivedAckForUnsentData(uint32_t seq)
{
EV_INFO << "ACK acks something not yet sent, sending immediate ACK" << endl;
}
diff --git a/src/inet/transportlayer/sctp/SctpAlg.h b/src/inet/transportlayer/sctp/SctpAlg.h
index 4cff91b8836..bf2e980a8c9 100644
--- a/src/inet/transportlayer/sctp/SctpAlg.h
+++ b/src/inet/transportlayer/sctp/SctpAlg.h
@@ -56,7 +56,7 @@ class INET_API SctpAlg : public SctpAlgorithm
virtual void receivedDuplicateAck() override;
- virtual void receivedAckForDataNotYetSent(uint32_t seq) override;
+ virtual void receivedAckForUnsentData(uint32_t seq) override;
virtual void sackSent() override;
diff --git a/src/inet/transportlayer/sctp/SctpAlgorithm.h b/src/inet/transportlayer/sctp/SctpAlgorithm.h
index b42d2ee90d0..e62fa285776 100644
--- a/src/inet/transportlayer/sctp/SctpAlgorithm.h
+++ b/src/inet/transportlayer/sctp/SctpAlgorithm.h
@@ -61,7 +61,7 @@ class INET_API SctpAlgorithm : public cObject
virtual void receivedDuplicateAck() = 0;
- virtual void receivedAckForDataNotYetSent(uint32_t seq) = 0;
+ virtual void receivedAckForUnsentData(uint32_t seq) = 0;
virtual void sackSent() = 0;
diff --git a/src/inet/transportlayer/tcp/ITcpCongestionControl.h b/src/inet/transportlayer/tcp/ITcpCongestionControl.h
new file mode 100644
index 00000000000..46368100242
--- /dev/null
+++ b/src/inet/transportlayer/tcp/ITcpCongestionControl.h
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2023 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_ITCPCONGESTIONCONTROL_H
+#define __INET_ITCPCONGESTIONCONTROL_H
+
+#include "inet/common/INETDefs.h"
+
+namespace inet {
+namespace tcp {
+
+class INET_API ITcpCongestionControl : public cObject
+{
+ public:
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) = 0;
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/ITcpRecovery.h b/src/inet/transportlayer/tcp/ITcpRecovery.h
new file mode 100644
index 00000000000..ae1b1a897cc
--- /dev/null
+++ b/src/inet/transportlayer/tcp/ITcpRecovery.h
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2023 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_ITCPRECOVERY_H
+#define __INET_ITCPRECOVERY_H
+
+#include "inet/transportlayer/tcp_common/TcpHeader.h"
+
+namespace inet {
+namespace tcp {
+
+class INET_API ITcpRecovery : public cObject
+{
+ public:
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength) = 0;
+
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) = 0;
+
+ virtual void receivedDuplicateAck() = 0;
+
+ /**
+ * Called when snd_una is about to advance, BEFORE the acked range
+ * [fromSeq, toSeq) is discarded from the send/rexmit queues, while the
+ * scoreboard entry for that range (transmit count, SACK state) is still
+ * valid. Lets a recovery algorithm tell reordering apart from loss.
+ */
+ virtual void segmentsAcked(uint32_t fromSeq, uint32_t toSeq) {}
+
+ /**
+ * Called after data was sent; the argument is the seqno of the first byte.
+ * Used by rate-limited recovery (RFC 6937 PRR) to account transmitted
+ * bytes, and by loss probes to (re)arm their timer.
+ */
+ virtual void dataSent(uint32_t fromSeq) {}
+
+ /**
+ * Called after a segment was retransmitted, over [fromSeq, toSeq).
+ */
+ virtual void segmentRetransmitted(uint32_t fromSeq, uint32_t toSeq) {}
+
+ /**
+ * Called when the retransmission timer expired, after the algorithm's own
+ * RTO handling. Lets a recovery algorithm capture undo state or start a
+ * spurious-RTO detection episode (RFC 5682 F-RTO).
+ */
+ virtual void onRexmitTimeout() {}
+
+ /**
+ * Called when the RACK reordering timer expired (RFC 8985 / Linux
+ * ICSK_TIME_REO_TIMEOUT) and loss detection has just marked further bytes
+ * lost, so the newly lost data can be retransmitted.
+ */
+ virtual void reoTimeout() {}
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/README b/src/inet/transportlayer/tcp/README
index 6849e719382..d9b46753845 100644
--- a/src/inet/transportlayer/tcp/README
+++ b/src/inet/transportlayer/tcp/README
@@ -2,24 +2,9 @@ TCP
===
This folder contains the "base part" of TCP, basically the operation
-described in RFC 793. This base algorithm can be extended and customized via
+described in RFC 9293. This base algorithm can be extended and customized via
"flavour" and "queue" classes that implement the TCPAlgorith, TCPSendQueue
and TCPReceiveQueue classes, and are selected via TCP's NED parameters.
-This implementation supports:
- - RFC 793 - Transmission Control Protocol
- - RFC 896 - Congestion Control in IP/TCP Internetworks
- - RFC 1122 - Requirements for Internet Hosts -- Communication Layers
- - RFC 1323 - TCP Extensions for High Performance
- - RFC 2018 - TCP Selective Acknowledgment Options
- - RFC 2581 - TCP Congestion Control
- - RFC 2883 - An Extension to the Selective Acknowledgement (SACK) Option for TCP
- - RFC 3042 - Enhancing TCP's Loss Recovery Using Limited Transmit
- - RFC 3390 - Increasing TCP's Initial Window
- - RFC 3517 - A Conservative Selective Acknowledgment (SACK)-based Loss Recovery
- Algorithm for TCP
- - RFC 3782 - The NewReno Modification to TCP's Fast Recovery Algorithm
-The old version of TCP is deprecated and can be found in the tcp_old directory under
-the tcp_old namespace.
The main class is TCP, which manages a set of TCPConnections. A TCP segment
is instance of the TCPSegment class. The TCP simple module must implement the ITCP
diff --git a/src/inet/transportlayer/tcp/Tcp.cc b/src/inet/transportlayer/tcp/Tcp.cc
index 4a058481365..3c3bbb17f61 100644
--- a/src/inet/transportlayer/tcp/Tcp.cc
+++ b/src/inet/transportlayer/tcp/Tcp.cc
@@ -18,6 +18,9 @@
#include "inet/networklayer/common/IpProtocolId_m.h"
#include "inet/networklayer/common/L3AddressTag_m.h"
+#include
+#include
+
#include "inet/networklayer/common/Icmpv4ErrorTag_m.h"
#include "inet/networklayer/common/Icmpv6ErrorTag_m.h"
#include "inet/transportlayer/common/TransportPseudoHeader_m.h"
@@ -51,6 +54,9 @@ void Tcp::initialize(int stage)
lastEphemeralPort = EPHEMERAL_PORTRANGE_START;
msl = par("msl");
+ alignOptions = par("alignOptions");
+ sendMssOption = par("sendMssOption");
+ fastOpenCookieCacheSize = par("fastopenCookieCacheSize");
WATCH(checksumMode);
WATCH(lastEphemeralPort);
@@ -173,11 +179,41 @@ void Tcp::handleLowerPacket(Packet *packet)
if (conn) {
TcpStateVariables *state = conn->getStateForUpdate();
if (state && state->ect) {
- // This may be true only in receiver side. According to RFC 3168, page 20:
- // pure acknowledgement packets (e.g., packets that do not contain
- // any accompanying data) MUST be sent with the not-ECT codepoint.
+ // This may be true only in receiver side.
+ // RFC 3168, page 20
+ // "pure acknowledgement packets (e.g., packets that do not contain
+ // any accompanying data) MUST be sent with the not-ECT codepoint."
state->gotCeIndication = (ecn == 3);
}
+ // AccECN: independent CE-packet counter, not routed through
+ // gotCeIndication (which nothing downstream of classic ECN actually reads --
+ // a pre-existing, separate gap, not fixed here). Counts from the moment
+ // negotiation completes, including CE marks on the handshake-completing 3rd ACK.
+ if (state && state->accEcnNegotiated && ecn == 3)
+ state->rcvCePkts++;
+
+ // AccECN TCP option: per-codepoint received-byte counters
+ // (E0B/E1B/CEB), incremented alongside rcvCePkts above but in bytes rather
+ // than packets. Same "from the moment negotiation completes" scope.
+ if (state && state->accEcnNegotiated) {
+ int payloadLength = packet->getByteLength() - tcpHeader->getHeaderLength().get();
+ if (payloadLength > 0) {
+ switch (ecn) {
+ case 1: state->rcvEct1Bytes += payloadLength; break; // IP_ECN_ECT_1
+ case 2: state->rcvEct0Bytes += payloadLength; break; // IP_ECN_ECT_0
+ case 3: state->rcvCeBytes += payloadLength; break; // IP_ECN_CE
+ default: break; // IP_ECN_NOT_ECT: not counted by any AccECN field
+ }
+ // Linux tp->accecn_minlen: the AccECN option sent next must
+ // include at least the field whose counter just changed
+ // (order-1 field numbers: ECT1=1, CE=2, ECT0=3 -- see
+ // tcp_ecnfield_to_accecn_optfield). Drives both the option
+ // fitting and the SACK-block reduction in its favor.
+ uint8_t neededField = ecn == 1 ? 1 : ecn == 3 ? 2 : ecn == 2 ? 3 : 0;
+ if (neededField > state->accEcnOptMinFields)
+ state->accEcnOptMinFields = neededField;
+ }
+ }
bool ret = conn->processTCPSegment(packet, tcpHeader, srcAddr, destAddr);
if (!ret)
@@ -400,6 +436,224 @@ void Tcp::addForkedConnection(TcpConnection *conn, TcpConnection *newConn, L3Add
tcpAppConnMap[newConn->socketId] = newConn;
}
+// SipHash-2-4, implemented clean-room from the public SipHash specification
+// (Aumasson & Bernstein, "SipHash: a fast short-input PRF"): 2 compression
+// rounds per 8-byte word, 4 finalization rounds, little-endian word loads,
+// final block carrying the input length in its top byte.
+static uint64_t sipHash24(const uint8_t key[16], const uint8_t *data, size_t len)
+{
+ auto le64 = [](const uint8_t *p) {
+ uint64_t v = 0;
+ for (int i = 7; i >= 0; i--)
+ v = (v << 8) | p[i];
+ return v;
+ };
+ auto rotl = [](uint64_t x, int b) { return (x << b) | (x >> (64 - b)); };
+ uint64_t k0 = le64(key), k1 = le64(key + 8);
+ uint64_t v0 = 0x736f6d6570736575ULL ^ k0;
+ uint64_t v1 = 0x646f72616e646f6dULL ^ k1;
+ uint64_t v2 = 0x6c7967656e657261ULL ^ k0;
+ uint64_t v3 = 0x7465646279746573ULL ^ k1;
+ auto round = [&]() {
+ v0 += v1; v1 = rotl(v1, 13); v1 ^= v0; v0 = rotl(v0, 32);
+ v2 += v3; v3 = rotl(v3, 16); v3 ^= v2;
+ v0 += v3; v3 = rotl(v3, 21); v3 ^= v0;
+ v2 += v1; v1 = rotl(v1, 17); v1 ^= v2; v2 = rotl(v2, 32);
+ };
+ size_t fullWords = len / 8;
+ for (size_t w = 0; w < fullWords; w++) {
+ uint64_t m = le64(data + w * 8);
+ v3 ^= m; round(); round(); v0 ^= m;
+ }
+ uint8_t last[8] = {};
+ for (size_t i = fullWords * 8; i < len; i++)
+ last[i % 8] = data[i];
+ last[7] = (uint8_t)(len & 0xff);
+ uint64_t m = le64(last);
+ v3 ^= m; round(); round(); v0 ^= m;
+ v2 ^= 0xff;
+ round(); round(); round(); round();
+ return v0 ^ v1 ^ v2 ^ v3;
+}
+
+std::vector Tcp::generateFastOpenCookie(const L3Address& localAddr, const L3Address& remoteAddr, int cookieBytes)
+{
+ // Linux-compatible derivation when a key is configured (fastopenKey param,
+ // the sysctl net.ipv4.tcp_fastopen_key format "xxxxxxxx-xxxxxxxx-xxxxxxxx-
+ // xxxxxxxx"): cookie = SipHash-2-4 over the incoming SYN's source address
+ // followed by its destination address (network byte order), keyed by the
+ // four hex words each laid out little-endian, with the 64-bit result
+ // written out little-endian.
+ // The "source" is the PEER here (the cookie is computed on the server for
+ // the client's SYN, and reproduced identically for validation).
+ const char *keyStr = par("fastopenKey");
+ uint32_t w[4];
+ if (cookieBytes == 8 && remoteAddr.getType() == L3Address::IPv4 && localAddr.getType() == L3Address::IPv4
+ && sscanf(keyStr, "%8x-%8x-%8x-%8x", &w[0], &w[1], &w[2], &w[3]) == 4)
+ {
+ uint8_t key[16];
+ for (int i = 0; i < 4; i++)
+ for (int b = 0; b < 4; b++)
+ key[i * 4 + b] = (uint8_t)(w[i] >> (8 * b)); // each word little-endian
+ uint8_t addrs[8];
+ uint32_t src = remoteAddr.toIpv4().getInt(); // peer = the SYN's source
+ uint32_t dst = localAddr.toIpv4().getInt();
+ for (int b = 0; b < 4; b++) {
+ addrs[b] = (uint8_t)(src >> (8 * (3 - b))); // network byte order
+ addrs[4 + b] = (uint8_t)(dst >> (8 * (3 - b)));
+ }
+ uint64_t h = sipHash24(key, addrs, sizeof(addrs));
+ std::vector cookie(8);
+ for (int b = 0; b < 8; b++)
+ cookie[b] = (uint8_t)(h >> (8 * b)); // little-endian result
+ return cookie;
+ }
+
+ // Lazily seed on first actual use (not in initialize()): an unconditional RNG
+ // draw at module init time would shift this module's RNG stream for every
+ // scenario, even ones that never touch TFO, breaking determinism/fingerprints
+ // for unrelated randomness sharing the same stream. This method is only ever
+ // reached once a peer has actually sent a TFO option, so a scenario that never
+ // exercises Fast Open -- including one with fastopenClientEnabled/
+ // fastopenServerEnabled defaulted to true, e.g. after the F7 default-enable gate
+ // -- never draws.
+ if (!fastOpenSecretSeeded) {
+ fastOpenSecret = ((uint64_t)(uint32_t)intuniform(0, 0x7fffffff) << 32) | (uint32_t)intuniform(0, 0x7fffffff);
+ fastOpenSecretSeeded = true;
+ }
+
+ // Clean-room, simulator-appropriate keyed mix -- intentionally NOT a port of
+ // Linux's AES/SipHash-based cookie cipher (RFC 7413's threat model of a blind
+ // off-path attacker doesn't apply to a non-adversarial simulator). Chains
+ // std::hash over the secret, the destination address, and a block
+ // counter to produce as many bytes as requested.
+ std::vector cookie(cookieBytes);
+ std::string base = std::to_string(fastOpenSecret) + "|" + remoteAddr.str();
+ std::hash hasher;
+ for (int block = 0; block * (int)sizeof(size_t) < cookieBytes; block++) {
+ size_t h = hasher(base + "|" + std::to_string(block));
+ const uint8_t *bytes = reinterpret_cast(&h);
+ for (size_t i = 0; i < sizeof(size_t) && (int)(block * sizeof(size_t) + i) < cookieBytes; i++)
+ cookie[block * sizeof(size_t) + i] = bytes[i];
+ }
+ return cookie;
+}
+
+bool Tcp::getFastOpenCookie(const L3Address& remoteAddr, std::vector& cookie) const
+{
+ auto it = fastOpenCookieCache.find(remoteAddr);
+ // an entry may hold ONLY a cached MSS (tcp_metrics semantics, see
+ // updateFastOpenCachedMss below) -- an empty cookie is "no cookie cached"
+ if (it == fastOpenCookieCache.end() || it->second.cookie.empty())
+ return false;
+ cookie = it->second.cookie;
+ return true;
+}
+
+void Tcp::updateFastOpenCachedMss(const L3Address& remoteAddr, uint32_t peerMss)
+{
+ if (peerMss == 0)
+ return;
+ auto it = fastOpenCookieCache.find(remoteAddr);
+ if (it != fastOpenCookieCache.end()) {
+ it->second.peerMss = peerMss;
+ return;
+ }
+ // No entry yet: create a cookie-less one. Linux keeps the peer MSS in
+ // tcp_metrics for EVERY connection, independent of a TFO cookie -- a
+ // cookie-less-mode connect still caps its next SYN payload by the last
+ // advertised MSS (cookie-less-sendto pins 900 = 940 - 40 from the
+ // previous, cookie-free connection).
+ setFastOpenCookie(remoteAddr, std::vector(), peerMss);
+}
+
+uint32_t Tcp::getFastOpenCachedMss(const L3Address& remoteAddr) const
+{
+ auto it = fastOpenCookieCache.find(remoteAddr);
+ return it == fastOpenCookieCache.end() ? 0 : it->second.peerMss;
+}
+
+void Tcp::setFastOpenCookie(const L3Address& remoteAddr, const std::vector& cookie, uint32_t peerMss)
+{
+ if (fastOpenCookieCache.find(remoteAddr) == fastOpenCookieCache.end()
+ && (int)fastOpenCookieCache.size() >= fastOpenCookieCacheSize)
+ {
+ // Bound the cache: evict an arbitrary entry (not LRU -- this is a small
+ // per-Tcp-module in-memory map, not full RFC 7413 persistence semantics;
+ // a real LRU is unnecessary machinery for a cache whose only job in a
+ // simulation is not growing unboundedly across thousands of destinations).
+ fastOpenCookieCache.erase(fastOpenCookieCache.begin());
+ }
+ // Update in place: the escalation counter survives storing a cookie
+ // (Linux tcp_fastopen_cache_set() writes tfom->cookie without touching
+ // tfom->try_exp), and so does the option form, which the caller sets
+ // separately right after.
+ auto& entry = fastOpenCookieCache[remoteAddr];
+ entry.cookie = cookie;
+ entry.peerMss = peerMss;
+}
+
+bool Tcp::getFastOpenUseExpOption(const L3Address& remoteAddr) const
+{
+ auto it = fastOpenCookieCache.find(remoteAddr);
+ if (it == fastOpenCookieCache.end())
+ return false;
+ // Linux tcp_fastopen_cache_get(): a cached cookie is echoed in its own form;
+ // with no cookie to echo, only the MIDDLE escalation value asks for the
+ // experimental encoding -- at 2 the experimental request has failed too and
+ // the standard kind is used from then on.
+ return it->second.cookie.empty() ? it->second.tryExp == 1 : it->second.exp;
+}
+
+void Tcp::setFastOpenCookieExpForm(const L3Address& remoteAddr, bool exp)
+{
+ auto it = fastOpenCookieCache.find(remoteAddr);
+ if (it != fastOpenCookieCache.end())
+ it->second.exp = exp;
+}
+
+void Tcp::noteFastOpenCookieRequestUnanswered(const L3Address& remoteAddr, bool usedExpOption)
+{
+ // Deliberately creates an entry when none exists: "request differently next
+ // time" is worth remembering even though no cookie was learned, which is
+ // precisely the unanswered-request case.
+ auto& entry = fastOpenCookieCache[remoteAddr];
+ // Linux tcp_fastopen_cache_set()'s guard: a cached cookie wins over any
+ // escalation, and the counter never goes backwards, so a later kind-34
+ // request that also goes unanswered cannot re-arm the experimental retry.
+ if (!entry.cookie.empty())
+ return;
+ uint8_t tryExp = usedExpOption ? 2 : 1;
+ if (tryExp > entry.tryExp)
+ entry.tryExp = tryExp;
+}
+
+void Tcp::clearFastOpenCookieCache()
+{
+ EV_INFO << "Fast Open: flushing " << fastOpenCookieCache.size() << " cached cookie(s)\n";
+ fastOpenCookieCache.clear();
+}
+
+bool Tcp::isActiveFastOpenDisabled() const
+{
+ return simTime() < fastOpenBlackholeDisableUntil;
+}
+
+void Tcp::recordFastOpenBlackhole()
+{
+ simtime_t timeout = par("fastopenBlackholeTimeout");
+ if (timeout == SIMTIME_ZERO)
+ return; // disabled (default)
+
+ fastOpenBlackholeDisableCount++;
+ // Exponential backoff capped at 64x, matching the kernel's tcp_fastopen_active_disable()
+ // 2^(n-1) multiplier (capped there too, at TFO_BHOLE_LOWCNT/backoff limits).
+ int multiplier = 1 << std::min(fastOpenBlackholeDisableCount - 1, 6); // 2^6 = 64
+ fastOpenBlackholeDisableUntil = simTime() + timeout * multiplier;
+ EV_INFO << "Fast Open: disabling active TFO after suspected blackhole (count="
+ << fastOpenBlackholeDisableCount << ", until t=" << fastOpenBlackholeDisableUntil << ")\n";
+}
+
void Tcp::addSockPair(TcpConnection *conn, L3Address localAddr, L3Address remoteAddr, int localPort, int remotePort)
{
// update addresses/ports in TcpConnection
diff --git a/src/inet/transportlayer/tcp/Tcp.h b/src/inet/transportlayer/tcp/Tcp.h
index e33788ebc68..7d3161229c2 100644
--- a/src/inet/transportlayer/tcp/Tcp.h
+++ b/src/inet/transportlayer/tcp/Tcp.h
@@ -161,6 +161,36 @@ class INET_API Tcp : public TransportProtocolBase
TcpAppConnMap tcpAppConnMap;
TcpConnMap tcpConnMap;
+ // TCP Fast Open (RFC 7413): per-module cookie generation secret and client-role
+ // cookie cache (destination address -> last-learned cookie). Not cryptographically
+ // strong -- RFC 7413's threat model (blind off-path attackers) doesn't apply to a
+ // non-adversarial simulator; see generateFastOpenCookie(). Seeded lazily, on first
+ // actual use, rather than in initialize(): an unconditional RNG draw at module init
+ // time would shift this module's RNG stream for every scenario -- including ones
+ // that never touch TFO -- breaking determinism for unrelated randomness sharing
+ // the same stream.
+ bool fastOpenSecretSeeded = false;
+ uint64_t fastOpenSecret = 0;
+ // Mirrors Linux's tcpm_fastopen: the cookie carries its OWN option form
+ // (cookie.exp), while tryExp is the separate escalation counter used while no
+ // cookie is cached -- 0 = request with kind 34, 1 = the kind-34 request went
+ // unanswered, retry as experimental, 2 = the experimental request went
+ // unanswered too, so stay with kind 34 for good. It only ever grows, which is
+ // what makes the retry a single retry (fallback-exp-opt: "first FO, then FOEXP
+ // once, then back to FO forever").
+ struct FastOpenCacheEntry { std::vector cookie; uint32_t peerMss = 0; bool exp = false; uint8_t tryExp = 0; };
+ std::map fastOpenCookieCache; // per-destination cookie + learned peer MSS (~ Linux tcp_metrics)
+ int fastOpenCookieCacheSize = 0; // read once from the fastopenCookieCacheSize parameter at INITSTAGE_LOCAL
+
+ // TCP Fast Open active blackhole detection (RFC 7413 SS4.4-inspired; simplified
+ // two-trigger port of the kernel's tcp_fastopen_active_should_disable/_disable/
+ // _detect_blackhole design -- see recordFastOpenBlackhole()). Module-wide, not
+ // per-destination: a middlebox that blackholes TFO SYN+data for one destination
+ // is presumed likely to do so for others too, matching the kernel's own
+ // process-wide (not per-destination) disable state.
+ int fastOpenBlackholeDisableCount = 0;
+ simtime_t fastOpenBlackholeDisableUntil = SIMTIME_ZERO;
+
ushort lastEphemeralPort = static_cast(-1);
std::multiset usedEphemeralPorts;
long numSegmentsSent = 0;
@@ -182,6 +212,8 @@ class INET_API Tcp : public TransportProtocolBase
public:
ChecksumMode checksumMode = CHECKSUM_MODE_UNDEFINED;
int msl;
+ bool alignOptions = true;
+ bool sendMssOption = true;
public:
Tcp() {}
@@ -205,6 +237,72 @@ class INET_API Tcp : public TransportProtocolBase
*/
virtual void addSockPair(TcpConnection *conn, L3Address localAddr, L3Address remoteAddr, int localPort, int remotePort);
+ /**
+ * TCP Fast Open (RFC 7413): derive a cookie for remoteAddr from this module's
+ * secret. Deterministic for a fixed secret+address (a simple keyed mix, not a
+ * cryptographic MAC -- see fastOpenSecret). Not const: lazily seeds fastOpenSecret
+ * from the module's RNG on first call.
+ */
+ virtual std::vector generateFastOpenCookie(const L3Address& localAddr, const L3Address& remoteAddr, int cookieBytes);
+
+ /** TCP Fast Open: client-role cookie cache lookup. Returns false if nothing is cached for remoteAddr. */
+ virtual bool getFastOpenCookie(const L3Address& remoteAddr, std::vector& cookie) const;
+
+ /** TCP Fast Open: refresh the cached peer MSS without touching the cookie -- Linux
+ * tcp_rcv_fastopen_synack() calls tcp_fastopen_cache_set() on EVERY TFO SYN-ACK,
+ * updating the MSS even when the SYN-ACK carries no (new) cookie. No-op if nothing
+ * is cached for remoteAddr. */
+ virtual void updateFastOpenCachedMss(const L3Address& remoteAddr, uint32_t peerMss);
+
+ /** TCP Fast Open: client-role cookie cache update, called on learning a cookie from a peer's SYN-ACK. */
+ virtual void setFastOpenCookie(const L3Address& remoteAddr, const std::vector& cookie, uint32_t peerMss);
+
+ /** Peer MSS learned alongside the cached cookie; 0 if no cache entry. */
+ virtual uint32_t getFastOpenCachedMss(const L3Address& remoteAddr) const;
+
+ /**
+ * TCP Fast Open: drop every cached cookie, so the next active open falls back
+ * to a bare cookie REQUEST. This is what `ip tcp_metrics flush` does on Linux,
+ * and test harnesses need it to re-arm the cookie-request path mid-run.
+ */
+ virtual void clearFastOpenCookieCache();
+
+ /**
+ * TCP Fast Open option form for remoteAddr: true = the experimental kind-254 +
+ * 0xF989-magic encoding (RFC 7413 appendix A), false = the assigned kind 34.
+ * With a cookie cached, the form that cookie arrived in decides; without one,
+ * the unanswered-request escalation does (Linux tcp_fastopen_cache_get:
+ * "cookie->len <= 0 && tfom->try_exp == 1").
+ */
+ virtual bool getFastOpenUseExpOption(const L3Address& remoteAddr) const;
+
+ /** Record which option form the cached cookie for remoteAddr arrived in. */
+ virtual void setFastOpenCookieExpForm(const L3Address& remoteAddr, bool exp);
+
+ /**
+ * A cookie REQUEST to remoteAddr came back with no Fast Open option at all;
+ * usedExpOption tells which form it was sent in. Advances the escalation
+ * counter so the next request tries the other encoding exactly once.
+ */
+ virtual void noteFastOpenCookieRequestUnanswered(const L3Address& remoteAddr, bool usedExpOption);
+
+ /**
+ * TCP Fast Open active blackhole detection: true while active (data-attached)
+ * Fast Open is temporarily disabled module-wide after suspected middlebox
+ * interference. Checked from process_OPEN_ACTIVE's cache-lookup gate -- while
+ * true, a new connection attempt behaves as if no cookie were cached (an
+ * immediate bare cookie-request SYN, no deferral/data), even if one is.
+ */
+ virtual bool isActiveFastOpenDisabled() const;
+
+ /**
+ * TCP Fast Open active blackhole detection: record a suspected blackhole event
+ * (an RTO or an out-of-order RST on a connection whose SYN carried data/a
+ * cookie) and disable active Fast Open for an exponentially-growing (capped)
+ * duration. No-op if fastopenBlackholeTimeout == 0 (disabled by default).
+ */
+ virtual void recordFastOpenBlackhole();
+
virtual void removeConnection(TcpConnection *conn);
virtual void sendToIp(Packet *segment);
virtual void sendToApp(cMessage *msg);
diff --git a/src/inet/transportlayer/tcp/Tcp.ned b/src/inet/transportlayer/tcp/Tcp.ned
index c0973ce1cd3..9115ebf880f 100644
--- a/src/inet/transportlayer/tcp/Tcp.ned
+++ b/src/inet/transportlayer/tcp/Tcp.ned
@@ -46,9 +46,10 @@ import inet.transportlayer.contract.ITcp;
// -# The `limitedTransmitEnabled` parameter can be used to enable/disable the
// Limited Transmit algorithm (RFC 3042).
//
-// -# The `increasedIWEnabled` parameter can be used to change the initial window
-// from one segment (RFC 2001) (based on MSS) to maximal four segments
-// (min(4*MSS, max (2*MSS, 4380 bytes))) (RFC 3390).
+// -# The `initialWindow` parameter selects the initial congestion window:
+// one segment (RFC 2001), min(4*MSS, max(2*MSS, 4380 bytes)) (RFC 3390), or
+// IW10 (RFC 6928), which is the default. The older `increasedIWEnabled`
+// boolean does the same as "rfc3390" and is deprecated.
//
// -# The `advertisedWindow` parameter defines the amount of data TCP can
// receive without the socket being read by the local user. Note that
@@ -67,13 +68,15 @@ import inet.transportlayer.contract.ITcp;
// used on a connection if both endpoints have it enabled.
//
// -# The `timestampSupport` parameter enables the Timestamps option
-// (RFC 1323). Like SACK, it is only used if both endpoints support it.
+// (RFC 7323). Like SACK, it is only used if both endpoints support it.
//
-// -# The `ecnWillingness` parameter enables Explicit Congestion Notification
-// (RFC 3168). ECN is negotiated during the three-way handshake and is
-// only active if both endpoints are willing. When using the `DcTcp`
-// algorithm, the `dctcpGamma` parameter controls the EWMA gain for
-// estimating the congestion level (RFC 8257).
+// -# The `tcpEcnMode` parameter selects Explicit Congestion Notification:
+// classic ECN (RFC 3168) or AccECN (RFC 9768), requested or accept-only.
+// Either way it is negotiated during the three-way handshake and is only
+// active if both endpoints agree. The older `ecnWillingness` boolean is
+// deprecated in favor of it. When using the `DcTcp` algorithm, the
+// `dctcpGamma` parameter controls the EWMA gain for estimating the
+// congestion level (RFC 8257).
//
// -# The `pmtudEnabled` parameter enables Path MTU Discovery (RFC 1191,
// RFC 1981). When enabled, IPv4 segments are sent with the DF bit set.
@@ -84,129 +87,427 @@ import inet.transportlayer.contract.ITcp;
// - if you do active OPEN, then send data and close before the connection
// has reached ESTABLISHED, the connection will go from SYN_SENT to CLOSED
// without actually sending the buffered data. This is consistent with
-// RFC 793 but may not be what you'd expect.
+// RFC 9293 but may not be what you'd expect.
// - handling segments with SYN+FIN bits set (esp. with data too) is
// inconsistent across TCPs, so check this one if it's of importance
//
// Standards
//
-// Implementation is based on the following RFCs:
-// - RFC 793 - Transmission Control Protocol
-// - RFC 896 - Congestion Control in IP/TCP Internetworks
-// - RFC 1122 - Requirements for Internet Hosts -- Communication Layers
-// - RFC 1323 - TCP Extensions for High Performance
-// - RFC 2018 - TCP Selective Acknowledgment Options
-// - RFC 2581 - TCP Congestion Control
-// - RFC 2883 - An Extension to the Selective Acknowledgement (SACK) Option for TCP
-// - RFC 3042 - Enhancing TCP's Loss Recovery Using Limited Transmit
-// - RFC 3390 - Increasing TCP's Initial Window
-// - RFC 3517 - A Conservative Selective Acknowledgment (SACK)-based Loss Recovery
-// Algorithm for TCP
-// - RFC 3782 - The `NewReno` Modification to TCP's Fast Recovery Algorithm
-// - RFC 1191 - Path MTU Discovery
-// - RFC 1981 - Path MTU Discovery for IP version 6
-// - RFC 3168 - The Addition of Explicit Congestion Notification (ECN) to IP
-// - RFC 8257 - Data Center TCP (DCTCP): ECN Marking at the Data Sender
-//
-// Implemented features include the following:
-// - all RFC 793 TCP states and state transitions
-// - connection setup and teardown as in RFC 793
-// - generally, RFC 793-compliant segment processing
-// - all socket commands and indications
-// - receive buffer to cache above-sequence data and data not yet forwarded
-// to the user
-// - CONN-ESTAB timer, SYN-REXMIT timer, 2MSL timer, FIN-WAIT-2 timer
-// - selective acknowledgements aka. SACK (RFC 2018 and RFC 2883)
-// - RFC 3517 - SACK-based Loss Recovery algorithm which is a conservative
-// replacement of the fast recovery algorithm (RFC2581) integrated into
-// `TcpReno` but not into `TcpNewReno`, `TcpTahoe`, `TcpNoCongestionControl`, and `DumbTcp`.
-// - changes from RFC 2001 to RFC 2581:
-// - ACK generation (ack_now = true) RFC 2581, page 6: "(...) a Tcp receiver SHOULD send an immediate ACK
-// when the incoming segment fills in all or part of a gap in the sequence space."
-// - TCP header options:
-// - EOL: End of option list.
-// - NOP: Padding bytes, currently needed for SACK_PERMITTED and SACK.
-// - MSS: The value of snd_mss (SMSS) is set to the minimum of snd_mss
-// (local parameter) and the value specified in the MSS option
-// received during connection startup. Based on [RFC 2581, page 1].
-// - WS: Window Scale option, based on RFC 1323.
-// - SACK_PERMITTED: SACK can only be used if both nodes sent SACK_-
-// PERMITTED during connection startup.
-// - SACK: SACK option, based on RFC 2018, RFC 2883, and RFC 3517.
-// - TS: Timestamps option, based on RFC 1323.
-// - flow control: finite receive buffer size (initiated by the parameter
-// `advertisedWindow`). If the receive buffer is exhausted (by out-of-order
-// segments) and the payload length of a newly received segment
-// is higher than free receiver buffer, the new segment will be dropped.
-// Such drops are recorded in `tcpRcvQueueDropsVector`.
-// - Path MTU Discovery (RFC 1191, RFC 1981): when enabled, the DF bit is set
-// on IPv4 segments. Upon receiving an ICMPv4 Fragmentation Needed or ICMPv6
-// Packet Too Big error, the MSS is reduced accordingly and the offending
-// segment is retransmitted. After a configurable timeout (`pmtudTimeout`),
-// the original MSS is restored to probe for an increased path MTU.
-// - Explicit Congestion Notification (RFC 3168): ECN-setup during the
-// three-way handshake, ECE/CWR flag handling, and CE codepoint processing.
-// - Data Center TCP (RFC 8257): DCTCP congestion control based on ECN
-// feedback, implemented in the `DcTcp` algorithm class.
-//
-// The `TcpNewReno`, `TcpReno`, and `TcpTahoe` algorithms implement:
-// - RFC 1122 - delayed ACK algorithm (optional) with 200ms timeout
-// - RFC 896 - Nagle's algorithm (optional)
-// - Jacobson's and Karn's algorithms for round-trip time measurement and
-// adaptive retransmission
-// - `TcpTahoe` (Fast Retransmit), `TcpReno` (Fast Retransmit and Fast Recovery),
-// `TcpNewReno` (Fast Retransmit and Fast Recovery)
-// - RFC 3390 - Increased Initial Window (optional) integrated into `TcpBaseAlg`
-// (can be used for `TcpNewReno`, `TcpReno`, `TcpTahoe`, and `TcpNoCongestionControl` but not
-// for `DumbTcp`).
-// - RFC 3042 - Limited Transmit algorithm (optional) integrated into `TcpBaseAlg`
-// (can be used for `TcpNewReno`, `TcpReno`, `TcpTahoe`, and `TcpNoCongestionControl` but not
-// for `DumbTcp`).
-//
-// Additional congestion control algorithms: `TcpVegas`, `TcpWestwood`, `DcTcp`.
-//
-// Missing bits:
-// - URG and PSH bits not handled. Receiver always acts as if PSH was set
-// on all segments: always forwards data to the app as soon as possible.
-// - no RECEIVE command. Received data is always forwarded to the app as
-// soon as possible, as if the app issued a very large RECEIVE request
-// at the beginning. This means there's currently no flow control
-// between Tcp and the app.
-// - all timeouts are precisely calculated: timer granularity (which is caused
-// by "slow" and "fast" i.e. 500ms and 200ms timers found in many *nix Tcp
-// implementations) is not simulated
-//
-// `TcpNewReno`, `TcpReno`, and `TcpTahoe` issues and missing features:
-// - KEEP-ALIVE not implemented (idle connections never time out)
-// - Nagle's algorithm (RFC 896) possibly not precisely implemented
-//
-// The above problems should be relatively easy to fix.
+// The documents below are grouped by area and ordered, within each area, by how
+// central they are to a working connection. Unless an entry lists deviations, the
+// feature is meant to follow its RFC; where deviations are listed they are
+// deliberate, not known bugs.
+//
+// Two recurring reasons for a deviation are worth stating once. First, the module
+// is continuously validated against real Linux behavior (the differential
+// packetdrill oracle in the `inet-gpl` package's `tests/oracle`), so where Linux
+// and an RFC disagree on something observable on the wire, the tie is broken in
+// favor of Linux; such entries are marked "Linux parity". Second, several
+// Linux-parity behaviors are gated behind default-off parameters so that existing
+// simulation results do not shift under users unannounced; those name the
+// parameter that turns them on.
+//
+// 1. Core protocol
+//
+// - RFC 9293 - Transmission Control Protocol (TCP)
+// The base specification: every state and transition, connection setup and
+// teardown, segment acceptability, sequence-number arithmetic, the CONN-ESTAB,
+// SYN-REXMIT, 2MSL and FIN-WAIT-2 timers, and the socket command set.
+// Deviations:
+// - The urgent pointer is carried and tracked, but urgent data is never
+// delivered out of band: `TCP_I_URGENT_DATA` is never produced, and a
+// receiver treats URG-marked bytes as ordinary data.
+// - PSH is set on outgoing segments but ignored on arrival: the receiver
+// always delivers as early as it can, as if PSH were set on everything.
+// (What drives PSH on the sending side is Linux's rule, not the RFC's --
+// see `pushSegmentsOnWriteBoundary`.)
+// - Checksums are declared correct rather than computed, unless
+// `checksumMode` is set to "computed".
+// - Timers are exact. The 200 ms / 500 ms "fast and slow tick" quantization
+// that BSD-derived stacks impose on every timeout is not simulated.
+// - Initial sequence numbers come from the simulation clock, or from
+// `initialSendSequenceNumber`. RFC 6528, "Defending against Sequence
+// Number Attacks", specifies a hashed generator; it is not implemented.
+// - The MSS this host announces defaults to an MTU-derived 1460 (IPv4) or
+// 1440 (IPv6) rather than RFC 1122's conservative 536/1220. Those RFC
+// values are still what a *missing* MSS option from the peer implies.
+//
+// - RFC 1122 - Requirements for Internet Hosts -- Communication Layers
+// Delayed ACKs, Nagle's algorithm, silly-window-syndrome avoidance on both
+// sides, and keepalive (`keepAliveEnabled`, off by default as on Linux).
+// Deviations:
+// - The delayed-ACK timer is a fixed 200 ms and also fires after
+// `delayedAckFrameCount` segments; RFC 1122 permits up to 500 ms.
+// `adaptiveDelayedAcks` replaces the fixed timer with Linux's adaptive ATO
+// plus its quickack budget and interactive "pingpong" detection.
+// (Linux parity, opt-in.)
+// - Nagle follows Minshall's variant -- a trailing partial segment is held
+// only while an earlier SMALL segment is still unacknowledged -- rather
+// than the plain "any unacknowledged data holds it" rule. (Linux parity.)
+// - Receiver-side SWS avoidance follows the Stevens/FreeBSD formulation and
+// ordering rather than the pseudo-code in RFC 9293.
+//
+// - RFC 6298 - Computing TCP's Retransmission Timer
+// Jacobson's and Karn's algorithms, exponential backoff, `initialRto` = 1 s.
+// Deviations:
+// - `minRexmitTimeout` defaults to 200 ms, Linux's floor, where section 2.4
+// recommends 1 s (while explicitly allowing a lower value). (Linux parity.)
+// - With `seedRttFromHandshake` the estimator starts from the SYN/SYN-ACK
+// round trip rather than from the first data ACK. RFC 6298 does not
+// describe this; Linux does it. (Linux parity, on by default.)
+//
+// - RFC 5961 - Improving TCP's Robustness to Blind In-Window Attacks
+// Section 5.2 only: an ACK below SND.UNA - MAX.SND.WND is discarded and
+// answered with a challenge ACK.
+// Deviations: the blind RST (section 3) and blind SYN (section 4)
+// mitigations, and the global challenge-ACK rate limit, are not implemented.
+//
+// - RFC 5461 - TCP's Reaction to Soft Errors
+// A soft ICMP error never aborts an established connection; hard errors abort
+// a connection that is still being set up.
+//
+// - RFC 5927 - ICMP Attacks against TCP
+// The sequence number quoted inside an ICMP error is validated against the
+// connection before the error is acted on.
+//
+// - RFC 2525 - Known TCP Implementation Problems
+// Section 2.5, "failure to retain above-sequence data": out-of-order segments
+// are buffered rather than discarded.
+//
+// - RFC 793 - Transmission Control Protocol (historic, obsoleted by RFC 9293)
+// Cited in the code only where the two texts differ in wording. RFC 9293
+// governs.
+//
+// 2. Header options and high-performance extensions
+//
+// - RFC 7323 - TCP Extensions for High Performance
+// Window Scale and Timestamps, both negotiated on the SYN exchange, plus PAWS
+// (with the specified 24-day idle threshold). Timestamps are also what drives
+// RTT measurement and the Eifel and RACK machinery below.
+// Deviations: the timestamp clock is the simulation clock; `windowScalingFactor`
+// may pin the shift count instead of deriving it from `advertisedWindow`.
+// Obsoletes RFC 1323, "TCP Extensions for High Performance", which older
+// comments and parameter descriptions still refer to.
+//
+// - RFC 2018 - TCP Selective Acknowledgment Options
+// SACK-Permitted and SACK, used only when both endpoints offer them.
+//
+// - RFC 2883 - An Extension to the Selective Acknowledgement (SACK) Option for
+// TCP (D-SACK)
+// Reported on duplicate arrivals and consumed by the spurious-retransmission
+// undo below (`dsackEnabled`).
+//
+// Options recognized on the wire: EOL, NOP, MSS, Window Scale,
+// SACK-Permitted, SACK, Timestamps, the AccECN options of section 5 (kinds
+// 172 and 174), and the Fast Open option in both its assigned (kind 34) and
+// experimental (kind 254) forms.
+//
+// 3. Congestion control
+//
+// - RFC 5681 - TCP Congestion Control
+// Slow start, congestion avoidance, fast retransmit and fast recovery; the
+// common substrate under every algorithm class. `TcpTahoe` implements fast
+// retransmit only, `TcpReno` and `TcpNewReno` add fast recovery.
+// Deviations:
+// - The congestion window is maintained in bytes rather than segments. This
+// is a representation choice, but it does surface: where the reference
+// counts packets (for example when deciding how large a burst may be), the
+// byte-valued window has to be divided by the effective MSS first.
+// - The default initial window is RFC 6928's IW10, not RFC 5681's 2-4
+// segments (see `initialWindow`).
+// Predecessors cited in the code: RFC 2001, "TCP Slow Start, Congestion
+// Avoidance, Fast Retransmit, and Fast Recovery Algorithms" (also the name of
+// `initialWindow = "rfc2001"`, one segment), and RFC 2581, "TCP Congestion
+// Control"; both are obsoleted by RFC 5681.
+//
+// - RFC 6928 - Increasing TCP's Initial Window
+// IW10, the default (`initialWindow = "rfc6928"`). A route-supplied initial
+// window may override it (`initialCwnd`), as `initcwnd` does on Linux.
+//
+// - RFC 3390 - Increasing TCP's Initial Window
+// Selectable as `initialWindow = "rfc3390"`. The older `increasedIWEnabled`
+// boolean is deprecated in favor of it.
+//
+// - RFC 9438 - CUBIC for Fast and Long-Distance Networks (obsoletes RFC 8312,
+// "CUBIC for Fast Long-Distance Networks")
+// The default algorithm (`tcpAlgorithmClass = "TcpCubic"`), including the
+// HyStart slow-start exit, which RFC 9438 describes as optional.
+// Deviations: the TCP-friendliness estimator keeps the reference
+// implementation's fixed scale, derived from beta = 717/1024, so changing
+// `cubicBeta` alters the multiplicative decrease without rescaling the
+// friendliness term.
+//
+// - RFC 8257 - Data Center TCP (DCTCP): TCP Congestion Control for Data Centers
+// ECN-feedback-driven congestion control, in the `DcTcp` algorithm class.
+//
+// - RFC 3042 - Enhancing TCP's Loss Recovery Using Limited Transmit
+// On by default (`limitedTransmitEnabled`); available to every algorithm
+// class except `DumbTcp`.
+//
+// - RFC 3465 - TCP Congestion Control with Appropriate Byte Counting
+// (experimental)
+// Applied in `TcpCubic`'s slow start, so that a delayed-ACK receiver does not
+// halve the growth rate. Deliberately NOT applied in the classic
+// `TcpTahoe`/`TcpReno`/`TcpNewReno`/`TcpWestwood` flavours, which grow by one
+// SMSS per ACK as RFC 5681 specifies.
+//
+// Also available, and not RFC-specified: `TcpVegas` and `TcpWestwood` (both
+// from the research literature), `TcpNoCongestionControl`, and `DumbTcp` --
+// the last two intentionally simplified, for teaching and for isolating other
+// behavior in tests.
+//
+// 4. Loss detection and recovery
+//
+// - RFC 8985 - The RACK-TLP Loss Detection Algorithm for TCP
+// The default (`lossDetectionMode = "rack"`, `tlpEnabled`): time-based loss
+// detection plus tail loss probes. Requires SACK.
+//
+// - RFC 6675 - A Conservative Loss Recovery Algorithm Based on Selective
+// Acknowledgment (SACK) for TCP
+// The SACK recovery engine, selected when SACK was negotiated by an algorithm
+// class that has a recovery engine at all -- `TcpCubic`, `TcpReno` and
+// `TcpNewReno`. (`TcpTahoe`, `TcpVegas`, `TcpWestwood`, `TcpNoCongestionControl`
+// and `DumbTcp` have none.) Also reachable as the loss detector, via
+// `lossDetectionMode = "dupthresh"`.
+// Deviations: DupThresh is dynamic by default rather than fixed at three --
+// see RFC 4653 below.
+//
+// - RFC 6582 - The NewReno Modification to TCP's Fast Recovery Algorithm
+// The recovery engine `TcpNewReno` and `TcpCubic` fall back to when SACK was
+// not negotiated. `TcpReno` falls back to plain RFC 5681 recovery instead.
+//
+// - RFC 6937 - Proportional Rate Reduction for TCP
+// The default pacing of the window down during recovery (`prrEnabled`),
+// replacing RFC 5681's cwnd inflation and halving. Requires SACK.
+//
+// - RFC 5682 - Forward RTO-Recovery (F-RTO): An Algorithm for Detecting
+// Spurious Retransmission Timeouts with TCP
+// SACK-enhanced spurious-timeout detection (`frtoEnabled`, on by default,
+// matching Linux's `tcp_frto = 2`).
+//
+// - RFC 3522 - The Eifel Detection Algorithm for TCP
+// Timestamp-based detection of a spurious retransmission; together with
+// D-SACK it drives the cwnd/ssthresh undo (`lossUndoEnabled`).
+//
+// - RFC 4653 - Improving the Robustness of TCP to Non-Congestion Events
+// The adaptive-reordering idea, not the full NCR algorithm: the reordering
+// degree grows when SACK reveals reordering, so persistent reordering stops
+// causing spurious fast retransmits (`adaptiveReorderingEnabled`,
+// `maxReordering`).
+//
+// - RFC 3517 - A Conservative Selective Acknowledgment (SACK)-based Loss
+// Recovery Algorithm for TCP (historic, obsoleted by RFC 6675)
+// Referenced in the code for the classic DupThresh formulation.
+//
+// 5. Explicit Congestion Notification
+//
+// - RFC 3168 - The Addition of Explicit Congestion Notification (ECN) to IP
+// ECN negotiation on the handshake, ECE/CWR handling, and CE processing
+// (`tcpEcnMode = "rfc3168"`, or "passive" to accept but not request).
+//
+// - RFC 9768 - More Accurate ECN Feedback in TCP (AccECN)
+// The AccECN handshake, the three-bit ACE counter, and the AccECN option
+// (`tcpEcnMode = "accecn"` / "accecn-passive", `accEcnOptionEnabled`).
+//
+// - RFC 8311 - Relaxing Restrictions on Explicit Congestion Notification (ECN)
+// Experimentation
+// What permits the reserved-bit reuse AccECN depends on.
+//
+// - RFC 3540 - Robust Explicit Congestion Notification (ECN) Signaling with
+// Nonces (historic, obsoleted by RFC 8311)
+// NOT implemented: the nonce-sum bit it defines is instead the AE bit of
+// AccECN's counter.
+//
+// 6. Path MTU discovery
+//
+// - RFC 1191 - Path MTU Discovery
+// - RFC 1981 - Path MTU Discovery for IP version 6
+// `pmtudEnabled` sets DF on IPv4 segments; an ICMPv4 Fragmentation Needed or
+// ICMPv6 Packet Too Big reduces the MSS and retransmits the offending
+// segment. After `pmtudTimeout` the original MSS is restored, to probe for a
+// path that has grown.
+//
+// - RFC 4821 - Packetization Layer Path MTU Discovery
+// `mtuProbing` arms an upward binary search between `baseMss` and the peer's
+// MSS: one oversized probe segment is built from data already queued, and the
+// lower bound moves up when that segment is acknowledged.
+// Deviations:
+// - Upward search only. There is no black-hole detector, so the "enable
+// after a failure is detected" setting (`mtuProbing = 1`) behaves as off;
+// only `mtuProbing = 2` arms the search.
+// - A lost probe is recognized from the retransmission that covers it, and
+// lowers the upper bound. Linux reaches the same verdict from its loss
+// estimator instead (`tcp_fastretrans_alert`), so the two can disagree about
+// exactly WHEN a probe is written off, though not about the outcome.
+// - There is no reprobe timer: once the search has narrowed, it stays
+// narrowed for the life of the connection.
+// - Sending a probe does not decrement the congestion window. The reference
+// decrements it because its window counts packets and the probe replaces
+// several of them with one; a byte-valued window already charges the probe
+// exactly what it carries.
+//
+// 7. Connection setup acceleration
+//
+// - RFC 7413 - TCP Fast Open
+// Client and server, cookie request and validation, data on the SYN, the
+// fallback paths, and the Appendix A experimental option (kind 254 with the
+// 0xF989 magic) alongside the assigned kind 34.
+// Note, not a deviation: RFC 7413 leaves cookie construction to the server.
+// Set `fastopenKey` and the cookie is derived exactly as Linux derives it
+// (SipHash-2-4 over the address pair), so a simulated server and a real client
+// agree; leave it empty, the default, and an internal hash is used instead.
+//
+// 8. Recognized on the wire but not implemented
+//
+// The option kinds these documents define are listed -- commented out -- in the
+// header definitions (`TcpHeader.msg`), as a record of which kind numbers are
+// taken. Nothing generates them, and an arriving option of one of these kinds is
+// decoded as an unknown option rather than being recognized.
+//
+// - RFC 2385 - Protection of BGP Sessions via the TCP MD5 Signature Option
+// (obsoleted by RFC 5925)
+// - RFC 5925 - The TCP Authentication Option
+// - RFC 5482 - TCP User Timeout Option
+// - RFC 4782 - Quick-Start for TCP and IP
+// - RFC 4727 - Experimental Values in IPv4, IPv6, ICMPv4, ICMPv6, UDP, and TCP
+// Headers, and RFC 3692 - Assigning Experimental and Testing Numbers
+// Considered Useful. These reserve option kinds 253 and 254 for experiments.
+// Kind 253 is unused here; kind 254 is NOT -- it carries the experimental Fast
+// Open option of section 7, and is the one live enum member in this list.
+// - RFC 1072 - TCP Extensions for Long-Delay Paths (Echo / Echo Reply)
+// - RFC 1146 - TCP Alternate Checksum Options
+// - RFC 1644 - T/TCP -- TCP Extensions for Transactions Functional Specification
+// (the CC, CC.NEW and CC.ECHO options)
+// - RFC 1693 - An Extension to TCP: Partial Order Service
+// - RFC 6247 - Moving the Undeployed TCP Extensions RFC 1072, RFC 1106, RFC 1110,
+// RFC 1145, RFC 1146, RFC 1379, RFC 1644, and RFC 1693 to Historic Status
+// (the document that retired four of the entries above: RFC 1072, 1146,
+// 1644 and 1693)
+//
+// - RFC 7805 - Moving Outdated TCP Extensions and TCP-Related Documents to
+// Historic or Informational Status (the companion to RFC 6247 above; between
+// them they explain why most of this list is historic rather than merely
+// unimplemented)
+//
+// Remaining gaps
+//
+// - Urgent data, as described under RFC 9293 above.
+// - Flow control between TCP and the application exists but is opt-in: a
+// connection opened in "autoread" mode drains the receive queue to the
+// application immediately, so its advertised window never closes. Open it in
+// explicit-read mode (`autoRead = false` on the OPEN command, then READ
+// commands) for a receive buffer that fills up and applies back pressure.
+// A segment dropped because the buffer was exhausted is counted by the
+// `tcpRcvQueueDrops` statistic.
+// - Receive-buffer occupancy is always tracked at skb-truesize granularity, but
+// it bounds the advertised window only under `windowShrinkAllowed`, or when the
+// buffer is pinned (`receiveBufferLocked`, i.e. an application that issued
+// SO_RCVBUF). Otherwise the window follows unread bytes, because an unpinned
+// buffer grows on demand and its free space is not the binding constraint.
//
simple Tcp extends SimpleModule like ITcp
{
parameters:
@class(Tcp);
string checksumMode @enum("declared", "computed") = default("declared");
- int advertisedWindow = default(14 * this.mss); // In bytes, corresponds with the maximal receiver buffer capacity (Note: normally, NIC queues should be at least this size)
- bool delayedAcksEnabled = default(false); // Delayed ACK algorithm (RFC 1122) enabled/disabled
- bool nagleEnabled = default(true); // Nagle's algorithm (RFC 896) enabled/disabled
- bool limitedTransmitEnabled = default(false); // Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl)
- bool increasedIWEnabled = default(false); // Increased Initial Window (RFC 3390) enabled/disabled
- bool sackSupport = default(false); // Selective Acknowledgment (RFC 2018, 2883, 3517) support (header option) (SACK will be enabled for a connection if both endpoints support it)
- bool windowScalingSupport = default(false); // Window Scale (RFC 1323) support (header option) (WS will be enabled for a connection if both endpoints support it)
- int windowScalingFactor = default(-1); // Window Scaling Factor given as a shift count. Valid values are 0..14, and -1 for automatic selection (it chooses the smallest shift count that makes advertisedWindow representable in 16 bits)
- bool timestampSupport = default(false); // Timestamps (RFC 1323) support (header option) (TS will be enabled for a connection if both endpoints support it)
- int mss = default(536); // Maximum Segment Size (header option). Default 536 per RFC 1122.
+ int advertisedWindow @mutable = default(65535); // In bytes, corresponds with the maximal receiver buffer capacity (Note: normally, NIC queues should be at least this size). @mutable so a harness/app can model setsockopt(SO_RCVBUF) by updating it before a connection is configured.
+ bool delayedAcksEnabled = default(true); // Delayed ACK algorithm (RFC 1122) enabled/disabled
+ int delayedAckFrameCount = default(2); // number of frames after delayed acks are sent
+ bool nagleEnabled = default(true); // Nagle's algorithm (RFC 1122) enabled/disabled
+ bool limitedTransmitEnabled = default(true); // Limited Transmit algorithm (RFC 3042) enabled/disabled (can be used for TcpReno/TcpTahoe/TcpNewReno/TcpNoCongestionControl)
+ bool adaptiveDelayedAcks = default(false); // Linux-shaped receiver ACK dynamics on top of delayedAcksEnabled: a quickack budget (immediate ACKs at connection start, after out-of-order data, and after long idle), an ADAPTIVE delayed-ACK timeout (ATO: 40ms floor, tracks the inter-segment arrival gap, bounded by srtt and 200ms) instead of the fixed 200ms timer, and an interactive "pingpong" mode. Off = the classic fixed-timeout RFC 1122 behavior
+ bool pushSegmentsOnWriteBoundary = default(false); // Linux parity: set the PSH flag on the last segment carrying the final buffered byte of the current send (Linux always does this; PSH has no effect on INET's own receiver, so this is a pure wire-realism option). Default off pending a maintainer-gated default flip, like the other Linux-default parity switches.
+ bool increasedIWEnabled = default(false); // DEPRECATED: use initialWindow="rfc3390" instead. Increased Initial Window (RFC 3390) enabled/disabled
+ string initialWindow @enum("rfc2001","rfc3390","rfc6928") = default("rfc6928"); // initial congestion window: rfc2001=1 SMSS, rfc3390=min(4*MSS,max(2*MSS,4380)), rfc6928=IW10=min(10*MSS,max(2*MSS,14600))
+ bool sackSupport = default(true); // Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+ bool dsackEnabled = default(this.sackSupport); // Selective Acknowledgment (RFC 2018, 2883, 6675) support (header option) (SACK will be enabled for a connection if both endpoints support it)
+ bool windowScalingSupport = default(true); // Window Scale (RFC 7323) support (header option) (WS will be enabled for a connection if both endpoints support it)
+ int windowScalingFactor @mutable = default(-1); // Window Scaling Factor given as a shift count. Valid values are 0..14, and -1 for automatic selection (it chooses the smallest shift count that makes advertisedWindow representable in 16 bits). @mutable so a harness/app modeling setsockopt(SO_RCVBUF) can switch to auto (-1) at runtime.
+ bool timestampSupport @mutable = default(true); // Timestamps (RFC 7323) support (header option) (TS will be enabled for a connection if both endpoints support it)
+ int mss = default(-1); // Maximum Segment Size (RFC 9293) (header option), i.e. the largest segment text this host is willing to receive. -1 derives it from the address family once the peer is known: 1460 for IPv4, 1440 for IPv6. RFC 1122's conservative 536/1220 is what a MISSING MSS option from the peer implies, not this default
+ int mtuProbing = default(0); // Packetized Path MTU Discovery (RFC 4821), ~ Linux sysctl tcp_mtu_probing: 0 = off, 2 = search for a larger usable MTU from the start of every connection. 1 (Linux "only after a black hole is detected") behaves as 0 here, since INET has no black-hole detector
+ int baseMss = default(1024); // lower end of the RFC 4821 search, ~ Linux sysctl tcp_base_mss: the MSS assumed to work without probing. Effective only with mtuProbing
+ int pathMtu = default(0); // the route's MTU, the ceiling the RFC 4821 search may reach (Linux dst_mtu / icsk_pmtu_cookie). 0 = derive from the negotiated MSS. Effective only with mtuProbing
+ int initialCwnd = default(0); // initial congestion window in SEGMENTS, overriding initialWindow -- models a route's `initcwnd` attribute. 0 = derive from initialWindow
+ int notsentLowat @unit(B) = default(-1B); // TCP_NOTSENT_LOWAT (Linux sockopt, no RFC): write-readiness low-water mark on queued-but-not-yet-transmitted bytes (sendQueue bytes ahead of snd_nxt). Once exceeded, no further TCP_I_SEND_MSG indication is sent until it drops back at or below this value. -1B = disabled (no notsentLowat-driven signaling; independent of the runtime-settable sendQueueLimit/TCP_C_QUEUE_BYTES_LIMIT mechanism, which tracks total outstanding+unsent bytes instead).
int msl @unit(s) = default(120s); // Maximum Segment Lifetime
- string tcpAlgorithmClass @examples("TcpVegas", "TcpWestwood", "DcTcp", "TcpNewReno", "TcpReno", "TcpTahoe", "TcpNoCongestionControl") = default("TcpReno");
- int dupthresh = default(3); // Used for TcpTahoe, TcpReno, and SACK (RFC 3517) DO NOT change unless you really know what you are doing
+ string tcpAlgorithmClass @examples("TcpCubic", "TcpVegas", "TcpWestwood", "DcTcp", "TcpNewReno", "TcpReno", "TcpTahoe", "TcpNoCongestionControl") = default("TcpCubic");
+ int dupthresh = default(3); // Used for TcpTahoe, TcpReno, and SACK (RFC 6675) DO NOT change unless you really know what you are doing
+ bool frtoEnabled = default(true); // F-RTO spurious-RTO detection (RFC 5682, SACK-enhanced variant): after a retransmission timeout, an ACK/SACK covering data that was never retransmitted proves the original flight arrived and the timeout was spurious -- the pre-RTO cwnd/ssthresh are restored. ~ sysctl tcp_frto=2 (the Linux default)
+ bool tlpEnabled = default(true); // Tail Loss Probe (RFC 8985 section 7.2): probe ~2*SRTT after the last transmission so a lost tail is repaired without waiting for the RTO; requires SACK. ~ Linux sysctl tcp_early_retrans=3 (its default)
+ bool seedRttFromHandshake = default(true); // Seed the RTT estimator (srtt/rttvar/RTO) from the SYN<->SYN-ACK exchange, as Linux does. The first-flight RTO and TLP probe timeout become RTT-scaled instead of initialRto.
+ bool adaptiveReorderingEnabled = default(true); // Grow the reordering degree (dynamic DupThresh) when SACK reveals reordering, so persistent reordering stops causing spurious fast retransmits. Requires sackSupport.
+ int maxReordering = default(300); // Upper bound on the learned reordering degree in segments (Linux tcp_max_reordering).
+ bool lossUndoEnabled = default(true); // Undo a spurious cwnd/ssthresh reduction when D-SACK (RFC 2883) or timestamps (RFC 3522 Eifel) reveal the retransmission was unnecessary.
+ bool prrEnabled = default(true); // Proportional Rate Reduction (RFC 6937) during fast recovery instead of classic cwnd inflation/halving. Requires sackSupport.
+ string lossDetectionMode @enum("dupthresh","rack") = default("rack"); // SACK loss detection: classic DupThresh (RFC 6675) or RACK time-based (RFC 8985). RACK requires sackSupport.
+ double receiveBufferSize @unit(B) = default(-1B) @mutable; // receive-BUFFER capacity, decoupled from the advertised window (Linux sk_rcvbuf, initialized from tcp_rmem[1] / SO_RCVBUF): out-of-order data is buffered up to this limit even when it exceeds the advertised window. -1 = same as the advertised window (historical INET behavior)
+ bool receiveBufferLocked @mutable = default(false); // the application pinned receiveBufferSize with SO_RCVBUF (Linux SOCK_RCVBUF_LOCK): the buffer no longer grows under receive pressure, so its free space -- not the unread byte count -- is what bounds the advertised window, and an arrival that no longer fits is dropped. @mutable so a harness/app modeling setsockopt(SO_RCVBUF) can set it before a connection is configured
+ bool windowAutoTuning = default(false); // receiver window auto-tuning (Linux tcp_grow_window): the offered window starts at advertisedWindow and grows with received data toward a clamp derived from receiveBufferSize. Requires receiveBufferSize to be set.
+ double rackReoTimerGranularity @unit(s) = default(0s); // model the kernel's jiffy-quantized REO timeout arming (Linux usecs_to_jiffies(timeout)+1: delay rounded UP to this granularity plus one extra tick, e.g. 4ms for an HZ=250 kernel); a sub-tick RACK deadline then fires whole ticks later, letting a dupthresh-timed SACK enter recovery first, as real kernels do. 0 = exact (default INET behavior)
+ bool syncookiesAlways = default(false); // model Linux sysctl tcp_syncookies=2 (cookies on every passive open): the server's connection is rebuilt from the cookie, whose 2-bit MSS field quantizes the peer's advertised MSS DOWN to the IPv4 msstab {536, 1300, 1440, 1460} (net/ipv4/syncookies.c); other option state (wscale/SACK/TS) survives via the timestamp encoding and stays exact
+ bool windowShrinkAllowed = default(false); // model Linux sysctl tcp_shrink_window=1: the advertised window follows the receive buffer's genuine free space (__tcp_select_window's shrink branch) and may move the right edge DOWN as unread data accumulates -- occupancy is accounted at Linux's skb-truesize granularity and scaled by the measured payload/truesize ratio (tcp_scaling_ratio), so unread bytes cost what they cost a real socket buffer. Needs receiveBufferSize and an application that leaves data unread (e.g. a packetdrill explicit-read run) to differ from the default no-shrink policy
int initialSsthresh = default(0xFFFFFFFF); // Initial value for Slow Start threshold used in TahoeRenoFamily. The initial value of ssthresh SHOULD be set arbitrarily high (e.g., to the size of the largest possible advertised window) Without user interaction there is no limit...
+ // TcpCubic (RFC 9438) tuning; effective only with tcpAlgorithmClass=TcpCubic
+ double cubicBeta = default(0.7); // multiplicative-decrease factor applied to cwnd on loss. Note that the TCP-friendliness estimator keeps the kernel's fixed scale (derived from beta=717/1024), so changing this only alters the decrease itself
+ double cubicC = default(0.4); // scaling constant C of the cubic window-growth curve
+ bool cubicFastConvergence = default(true); // on a loss below the previous W_max, release extra window so a newly arrived competing flow can grow (RFC 9438 section 4.7)
+ bool cubicTcpFriendliness = default(true); // never grow slower than an AIMD(1, beta) Reno flow would (RFC 9438 section 4.3)
+ double cubicDelta @unit(s) = default(10ms); // settling time after a window reduction, during which RTT samples are ignored
+ int cubicCntClamp = default(20); // cap on the per-increment ACK count while no W_max is known yet (before the first loss)
+ bool hystartEnabled = default(true); // HyStart: leave slow start when the path appears full, instead of overshooting into loss
+ int hystartDetect = default(3); // HyStart detectors, as a bitmask: 1 = ACK train, 2 = delay increase, 3 = both
+ int hystartLowWindow = default(16); // smallest cwnd (in segments) at which HyStart starts looking for the exit point
+ double hystartAckDelta @unit(s) = default(2ms); // largest ACK spacing still counted as part of an ACK train
+ double hystartDelayMin @unit(s) = default(4ms); // lower clamp on the HyStart delay-increase threshold (an eighth of the minimum RTT)
+ double hystartDelayMax @unit(s) = default(16ms); // upper clamp on the same threshold (Linux HYSTART_DELAY_MAX)
double stopOperationExtraTime @unit(s) = default(0s); // Extra time after lifecycle stop operation finished
double stopOperationTimeout @unit(s) = default(2s); // Timeout value for lifecycle stop operation
- bool ecnWillingness = default(false); // True if willing to use ECN
+ bool ecnWillingness = default(false); // DEPRECATED -- use tcpEcnMode. true maps to tcpEcnMode="rfc3168" unless tcpEcnMode is ALSO explicitly set to something other than "off" (then a cRuntimeError is thrown -- same precedence rule as increasedIWEnabled vs initialWindow)
+ string tcpEcnMode @mutable @enum("off","passive","rfc3168","accecn","accecn-passive") = default("off"); // @mutable so a harness can model a mid-run "sysctl net.ipv4.tcp_ecn=N": the value is read per connection at open time, exactly as Linux samples it
+ // Value semantics modeled on Linux net.ipv4.tcp_ecn (0/1/2/3/4/5; AccECN owns 3/4/5):
+ // off ~ tcp_ecn=0: never request or accept any form of ECN (today's default)
+ // passive ~ tcp_ecn=2: accept a peer-initiated classic ECN-setup SYN, never
+ // initiate ECN on active OPEN. Linux's real out-of-box default.
+ // rfc3168 ~ tcp_ecn=1/4: always send a classic ECN-setup SYN on active OPEN;
+ // accept passively when listening. Equivalent to ecnWillingness=true.
+ // accecn ~ tcp_ecn=3: attempt AccECN negotiation on active OPEN and accept it
+ // when listening; fall back to rfc3168 or off per negotiation outcome.
+ // accecn-passive ~ tcp_ecn=5: accept AccECN when listening, never initiate it on
+ // active OPEN (mirrors "passive" but for AccECN).
+ bool accEcnOptionEnabled @mutable = default(false); // Send/parse the AccECN TCP option (E0B/E1B/CEB byte counters). Requires tcpEcnMode in {"accecn","accecn-passive"}
+ int accEcnOptionBeaconAcks = default(4); // Send the AccECN option at least once every N ACKs even when not otherwise demanded (simplified beaconing policy -- Linux's full demand/beacon/DSACK-triggered state machine is not ported)
+ bool accEcnOptionKindAlternates = default(true); // If true, alternate the AccECN option between kind 172 (E0B,CEB,E1B) and 174 (E1B,CEB,E0B) on successive sends (draft middlebox-robustness). If false, always emit kind 174 -- Linux's observed behavior.
double dctcpGamma = default(0.0625); // A fixed estimation gain for calculating dctcp_alpha (RFC 8257 4.2)
bool pmtudEnabled = default(false); // Path MTU Discovery (RFC 1191, RFC 1981) enabled/disabled
double pmtudTimeout @unit(s) = default(600s); // Time after which the original MSS is restored to probe for increased path MTU (RFC 1191, Section 6.3)
+ volatile int initialSendSequenceNumber = default(-1); // -1 means the value is calculated from the current simulation time
+ double initialRto @unit(s) = default(1s); // initial retransmission timeout, used until the first RTT measurement completes (RFC 6298 section 2.1 specifies 1s; 3s is the historical RFC 1122 value)
+ int synLinearTimeouts = default(4); // Linux net.ipv4.tcp_syn_linear_timeouts: number of initial CLIENT SYN retransmits fired at the (non-backed-off) initial RTO before exponential backoff begins; 0 = double from the first retransmit
+ int synRetries @mutable = default(-1); // Linux net.ipv4.tcp_syn_retries / per-socket TCP_SYNCNT: maximum number of SYN (and SYN-ACK) retransmissions before the connection attempt is aborted; -1 = INET's historical MAX_SYN_REXMIT_COUNT (12). @mutable so a test harness can inject the per-socket sockopt at runtime.
+ double minRexmitTimeout @unit(s) = default(0.2s); // minimum retransmission timeout
+ double maxRexmitTimeout @unit(s) = default(120s); // maximum retransmission timeout
+ int maxRexmitCount = default(12); // maximum retransmission count
+ double minPersistTimeout @unit(s) = default(5s); // minimum persist timeout
+ double maxPersistTimeout @unit(s) = default(60s); // maximum persist timeout
+ double delayedAckTimeout @unit(s) = default(200ms); // maximum time to wait before sending a delayed ACK
+ bool sendDataWithFirstAck = default(true); // send data in 3rd packet in SYN - SYN+ACK - ACK sequence
+ bool alignOptions = default(true); // enable/disable per-option predefined alignment
+ bool sendMssOption = default(true); // enable/disable MSS option sending
+ bool keepAliveEnabled = default(false); // TCP keepalive (RFC 1122 4.2.3.6). Off by default, like SO_KEEPALIVE on Linux
+ double keepAliveIdleTime @unit(s) = default(7200s); // idle time before the first keepalive probe (Linux tcp_keepalive_time)
+ double keepAliveInterval @unit(s) = default(75s); // interval between keepalive probes (Linux tcp_keepalive_intvl)
+ int keepAliveProbeCount = default(9); // number of unanswered probes before the connection is aborted (Linux tcp_keepalive_probes)
+ bool fastopenClientEnabled @mutable = default(false); // TCP Fast Open (RFC 7413) client role: attempt SYN-with-data when a cookie is cached. ~ sysctl tcp_fastopen bit 0 (TFO_CLIENT_ENABLE)
+ bool fastopenServerEnabled @mutable = default(false); // TCP Fast Open (RFC 7413) server role: accept and accelerate a validated-cookie SYN-with-data. ~ sysctl tcp_fastopen bit 1 (TFO_SERVER_ENABLE)
+ bool fastopenAcceptWithoutCookie @mutable = default(false); // Server role: accept SYN-with-data even when the SYN carries no Fast Open cookie option at all (RFC 7413 section 4.1.3's cookie-less mode). ~ sysctl tcp_fastopen bit 9 (0x200, TFO_SERVER_COOKIE_NOT_REQD). Only meaningful with fastopenServerEnabled.
+ bool fastopenExpOptionEnabled @mutable = default(false); // Understand the pre-standardization kind-254 + magic-0xF989 Fast Open option (RFC 7413 Appendix A). Emitted only in reply to a peer that used it -- a server echoes the client's form in its SYN-ACK, and a client that got no answer to a kind-34 request retries once with this one, as Linux does. Inert unless fastopenServerEnabled or fastopenClientEnabled is also true
+ bool fastopenLenientCookieValidation = default(true); // Accept any syntactically valid (4-16 byte) Fast Open cookie as valid without a keyed-hash check -- simulation-appropriate (no adversarial clients on the wire); set false to require an exact match against the locally generated cookie
+ int fastopenCookieBytes = default(8); // Length in bytes of the locally generated Fast Open cookie (RFC 7413 allows 4-16)
+ double fastopenBlackholeTimeout @unit(s) = default(0s); // Client-side active-TFO blackhole detection: base disable duration after a suspected middlebox interference event (RTO or out-of-order RST on a data-carrying TFO SYN), doubling (capped at 64x) on repeated events. 0s (default) disables the detector entirely, matching Linux's own shipped default
+ bool fastopenClientNoCookieRequired @mutable = default(false); // Linux net.ipv4.tcp_fastopen bit 0x4 (TFO_CLIENT_NO_COOKIE): the client sends SYN+data immediately with NO cookie (and no cookie-request) at all -- for networks where the server is known to accept cookie-less TFO
+ string fastopenKey @mutable = default(""); // Server-role Fast Open cookie key in Linux net.ipv4.tcp_fastopen_key format ("xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx", primary key only). When set, the 8-byte cookie is derived exactly as Linux does (SipHash-2-4 of source||destination IPv4 address under this key), so cookies are deterministic and interoperable with captures; when empty (default), a simulation-local keyed hash is used instead
+ int fastopenCookieCacheSize = default(1024); // Upper bound on the number of distinct destinations the client-role Fast Open cookie cache retains; evicts an arbitrary (not LRU) entry once full
@display("i=block/wheelbarrow");
@signal[tcpConnectionAdded];
@signal[tcpConnectionRemoved];
diff --git a/src/inet/transportlayer/tcp/TcpAlgorithm.h b/src/inet/transportlayer/tcp/TcpAlgorithm.h
index 5395a2e47b9..3fd79d5710d 100644
--- a/src/inet/transportlayer/tcp/TcpAlgorithm.h
+++ b/src/inet/transportlayer/tcp/TcpAlgorithm.h
@@ -8,8 +8,11 @@
#ifndef __INET_TCPALGORITHM_H
#define __INET_TCPALGORITHM_H
-#include "inet/transportlayer/tcp/TcpConnection.h"
#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/ITcpCongestionControl.h"
+#include "inet/transportlayer/tcp/ITcpRecovery.h"
+#include "inet/transportlayer/tcp/TcpConnection.h"
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
namespace inet {
namespace tcp {
@@ -25,6 +28,14 @@ class INET_API TcpAlgorithm : public cObject
protected:
TcpConnection *conn; // we belong to this connection
TcpStateVariables *state; // our state variables
+ simtime_t initialRto;
+ simtime_t minRexmitTimeout;
+ simtime_t maxRexmitTimeout;
+ int maxRexmitCount;
+ simtime_t minPersistTimeout;
+ simtime_t maxPersistTimeout;
+ simtime_t delayedAckTimeout;
+ bool sendDataWithFirstAck = true;
/**
* Create state block (TCB) used by this TCP variant. It is expected
@@ -67,7 +78,16 @@ class INET_API TcpAlgorithm : public cObject
* This method is necessary because the TcpConnection ptr is not
* available in the constructor yet.
*/
- virtual void initialize() {}
+ virtual void initialize() {
+ initialRto = conn->getTcpMain()->par("initialRto");
+ minRexmitTimeout = conn->getTcpMain()->par("minRexmitTimeout");
+ maxRexmitTimeout = conn->getTcpMain()->par("maxRexmitTimeout");
+ maxRexmitCount = conn->getTcpMain()->par("maxRexmitCount");
+ minPersistTimeout = conn->getTcpMain()->par("minPersistTimeout");
+ maxPersistTimeout = conn->getTcpMain()->par("maxPersistTimeout");
+ sendDataWithFirstAck = conn->getTcpMain()->par("sendDataWithFirstAck");
+ delayedAckTimeout = conn->getTcpMain()->par("delayedAckTimeout");
+ }
/**
* Called when the connection is going to ESTABLISHED from SYN_SENT or
@@ -99,10 +119,19 @@ class INET_API TcpAlgorithm : public cObject
*/
virtual void sendCommandInvoked() = 0;
+ /**
+ * Arm / cancel the "cork" flush timer, which force-flushes a TCP_CORK/MSG_MORE
+ * partial segment that could not be sent (Linux ICSK_TIME_PROBE0, fired at the
+ * RTO). Delegated to the algorithm because the RTO lives in its derived state.
+ * Non-pure with no-op defaults so flavours without a cork timer need no change.
+ */
+ virtual void scheduleCorkTimer() {}
+ virtual void cancelCorkTimer() {}
+
/**
* Called after receiving data which are in the window, but not at its
* left edge (seq != rcv_nxt). This indicates that either segments got
- * re-ordered in the way, or one segment was lost. RFC 1122 and RFC 2001
+ * re-ordered in the way, or one segment was lost. RFC 1122 and RFC 5681
* recommend sending an immediate ACK here (Fast Retransmit relies on
* that).
*/
@@ -110,12 +139,17 @@ class INET_API TcpAlgorithm : public cObject
/**
* Called after rcv_nxt got advanced, either because we received in-sequence
- * data ("text" in RFC 793 lingo) or a FIN. At this point, rcv_nxt has
+ * data ("text" in RFC 9293 lingo) or a FIN. At this point, rcv_nxt has
* already been updated. This method should take care to send or schedule
* an ACK some time.
*/
virtual void receiveSeqChanged() = 0;
+ /**
+ * Called after we received an ACK for which ackNo <= snd_una.
+ */
+ virtual void receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength) = 0;
+
/**
* Called after we received an ACK which acked some data (that is,
* we could advance snd_una). At this point the state variables
@@ -124,21 +158,30 @@ class INET_API TcpAlgorithm : public cObject
* (snd_una - firstSeqAcked). The dupack counter still reflects the old value
* (needed for Reno and NewReno); it'll be reset to 0 after this call returns.
*/
- virtual void receivedDataAck(uint32_t firstSeqAcked) = 0;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) = 0;
+
+ /**
+ * Called when snd_una is about to advance, BEFORE the acked range
+ * [fromSeq, toSeq) is discarded from the send/rexmit queues. At this point
+ * the scoreboard data for [fromSeq, toSeq) (transmit counts, SACK state) is
+ * still valid, so an algorithm can inspect it (e.g. to distinguish reordering
+ * from loss). Default-empty; overridden by flavours that need it.
+ */
+ virtual void segmentsAcked(uint32_t fromSeq, uint32_t toSeq) {}
/**
- * Called after we received a duplicate ACK (that is: ackNo == snd_una,
- * no data in segment, segment doesn't carry window update, and also,
- * we have unacked data). The dupack counter got already updated
- * when calling this method (i.e. dupacks == 1 on first duplicate ACK.)
+ * Whether this flavour implements SACK-based (RFC 6675) loss recovery.
+ * SACK is orthogonal to congestion control (as in Linux): a flavour that
+ * returns false will have SACK disabled even if the host is willing, so that
+ * turning sackSupport on by default does not break non-SACK flavours.
*/
- virtual void receivedDuplicateAck() = 0;
+ virtual bool supportsSackRecovery() const { return false; }
/**
* Called after we received an ACK for data not yet sent.
- * According to RFC 793 this function should send an ACK.
+ * According to RFC 9293 this function should send an ACK.
*/
- virtual void receivedAckForDataNotYetSent(uint32_t seq) = 0;
+ virtual void receivedAckForUnsentData(uint32_t seq) = 0;
/**
* Called after we sent an ACK. This hook can be used to cancel
@@ -171,6 +214,14 @@ class INET_API TcpAlgorithm : public cObject
*/
virtual void rttMeasurementCompleteUsingTS(uint32_t echoedTS) = 0;
+ /**
+ * Report a completed RTT measurement (segment sent at tSent, acked at
+ * tAcked) to the algorithm's estimator. Used by the connection for the
+ * handshake (SYN<->SYN-ACK) RTT seed; data-segment measurements are
+ * handled internally by the algorithm.
+ */
+ virtual void rttMeasurementComplete(simtime_t tSent, simtime_t tAcked) = 0;
+
/**
* Called before sending ACK. Determines whether to set ECE bit.
*/
@@ -181,6 +232,43 @@ class INET_API TcpAlgorithm : public cObject
* This function process ECN marks.
*/
virtual void processEcnInEstablished() = 0;
+
+ /**
+ * Returns the sender's estimation of total bytes in flight in the network.
+ */
+ virtual uint32_t getBytesInFlight() const = 0;
+
+ /**
+ * The smoothed round-trip time, or zero while no sample has been taken.
+ * Flavours without an RTT estimator (DumbTcp) keep the zero default, which
+ * every caller must read as "unknown".
+ */
+ virtual simtime_t getSrtt() const { return SIMTIME_ZERO; }
+
+ /**
+ * The new ssthresh when entering fast recovery, per this congestion-control
+ * flavour (Linux icsk_ca_ops->ssthresh). Rfc6675Recovery::step4() calls this
+ * instead of hardcoding FlightSize/2 so a flavour such as CUBIC can apply its own
+ * reduction factor (beta). Non-pure with a 0 default so flavours without a
+ * recovery engine (DumbTcp) need no change.
+ */
+ virtual uint32_t calculateSsthreshForFastRecovery() { return 0; }
+
+ /**
+ * The ssthresh this flavour's multiplicative decrease yields for a given flight
+ * size (Linux icsk_ca_ops->ssthresh with an explicit argument). Same role as
+ * calculateSsthreshForFastRecovery(), for the callers that have already computed
+ * the flight size they want the reduction taken from.
+ */
+ virtual uint32_t calculateSsthresh(uint32_t bytesInFlight) { return 0; }
+
+ /**
+ * The connection's loss-recovery strategy, or nullptr for flavours that do
+ * not use the ITcpRecovery split (DumbTcp, TcpNoCongestionControl, Vegas,
+ * Westwood). Lets the connection reach RACK/PRR/etc. without knowing the
+ * concrete algorithm class.
+ */
+ virtual ITcpRecovery *getRecovery() { return nullptr; }
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/TcpConnection.h b/src/inet/transportlayer/tcp/TcpConnection.h
index 85af2b5098f..583bd03c6fa 100644
--- a/src/inet/transportlayer/tcp/TcpConnection.h
+++ b/src/inet/transportlayer/tcp/TcpConnection.h
@@ -9,6 +9,11 @@
#ifndef __INET_TCPCONNECTION_H
#define __INET_TCPCONNECTION_H
+#include
+#include
+#include
+#include
+
#include "inet/common/SimpleModule.h"
#include "inet/networklayer/common/IcmpType_m.h"
#include "inet/networklayer/common/Icmpv6Type_m.h"
@@ -36,10 +41,19 @@ class TcpAlgorithm;
//@}
#define MAX_SYN_REXMIT_COUNT 12 // will only be used with SYN+ACK: with SYN CONN_ESTAB occurs sooner
+#define TFO_BLACKHOLE_RTO_THRESHOLD 2 // TCP Fast Open active blackhole detection: syn_rexmit_count value (i.e. the 3rd SYN transmission, matching the kernel's "timeouts == 2" check) that triggers a suspected-blackhole report for a data-carrying SYN
+
+// AccECN (draft-ietf-tcpm-accurate-ecn): state->ecnMode values, mirroring the
+// tcpEcnMode NED enum by index (Tcp.ned / TcpConnectionState.msg's ecnMode field).
+#define TCP_ECN_MODE_OFF 0
+#define TCP_ECN_MODE_PASSIVE 1
+#define TCP_ECN_MODE_RFC3168 2
+#define TCP_ECN_MODE_ACCECN 3
+#define TCP_ECN_MODE_ACCECN_PASSIVE 4
#define TCP_MAX_WIN 65535lu // 65535 bytes, largest value (16 bit) for (unscaled) window size
#define TCP_MAX_WIN_SCALED 0x3fffffffL // 2^30-1 bytes, largest value for scaled window size
#define MAX_SACK_BLOCKS 60 // will only be used with SACK
-#define PAWS_IDLE_TIME_THRESH (24 * 24 * 3600) // 24 days in seconds (RFC 1323)
+#define PAWS_IDLE_TIME_THRESH (24 * 24 * 3600) // 24 days in seconds (RFC 7323)
/**
* Manages a TCP connection. This class itself implements the TCP state
@@ -48,7 +62,7 @@ class TcpAlgorithm;
* associated with TCP state changes.
*
* The implementation largely follows the functional specification at the end
- * of RFC 793. Code comments extensively quote RFC 793 to make it easier
+ * of RFC 9293. Code comments extensively quote RFC 9293 to make it easier
* to understand.
*
* TcpConnection objects are not used alone -- they are instantiated and managed
@@ -92,27 +106,8 @@ class TcpAlgorithm;
class INET_API TcpConnection : public SimpleModule
{
protected:
- static simsignal_t tcpConnectionAddedSignal;
- static simsignal_t stateSignal; // FSM state
- static simsignal_t sndWndSignal; // snd_wnd
- static simsignal_t rcvWndSignal; // rcv_wnd
- static simsignal_t rcvAdvSignal; // current advertised window (=rcv_adv)
- static simsignal_t sndNxtSignal; // sent seqNo
- static simsignal_t sndAckSignal; // sent ackNo
- static simsignal_t rcvSeqSignal; // received seqNo
- static simsignal_t rcvAckSignal; // received ackNo (=snd_una)
- static simsignal_t unackedSignal; // number of bytes unacknowledged
- static simsignal_t dupAcksSignal; // current number of received dupAcks
- static simsignal_t pipeSignal; // current sender's estimate of bytes outstanding in the network
- static simsignal_t sndSacksSignal; // number of sent Sacks
- static simsignal_t rcvSacksSignal; // number of received Sacks
- static simsignal_t rcvOooSegSignal; // number of received out-of-order segments
- static simsignal_t rcvNASegSignal; // number of received not acceptable segments
- static simsignal_t sackedBytesSignal; // current number of received sacked bytes
- static simsignal_t tcpRcvQueueBytesSignal; // current amount of used bytes in tcp receive queue
- static simsignal_t tcpRcvQueueDropsSignal; // number of drops in tcp receive queue
- static simsignal_t tcpRcvPayloadBytesSignal; // amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
-
+ static simsignal_t deliveredCeSignal; // AccECN: cumulative resolved count of CE-marked packets the peer has reported via the ACE field
+ static simsignal_t deliveredCeBytesSignal; // AccECN: cumulative CE byte count from AccECN option evidence only (stays 0 if the peer never sends the option)
// connection identification by apps: socketId
int socketId = -1; // identifies connection within the app
@@ -128,7 +123,49 @@ class INET_API TcpConnection : public SimpleModule
int ttl = -1;
short dscp = -1;
short tos = -1;
+ // SO_TIMESTAMPING: unrelated to TcpConnectionState's
+ // ts_support/ts_enabled (the RFC 1323 TCP Timestamps wire option) -- this is the
+ // app-facing, no-wire-impact socket option gating TcpRxTimestampInd delivery. A
+ // plain TcpConnection member (like ttl/dscp/tos above), not a state field, since
+ // it must be settable via TCP_C_SETOPTION before state exists (app code may call
+ // TcpSocket::setTimestamping() before connect(), same as setTtl/setDscp/setTos).
+ bool rxTimestampingEnabled = false;
+ // Runtime TCP_NOTSENT_LOWAT (TcpSetNotsentLowatCommand): like
+ // rxTimestampingEnabled above, must survive arriving before state exists
+ // (a sockopt sent between bind() and connect()/listen()). INT_MIN = never
+ // set; otherwise applied over the notsentLowat module param in
+ // configureStateVariables(), and directly to state when set later.
+ int notsentLowatSockopt = INT_MIN;
+ // Runtime TCP_MAXSEG (TcpSetMaxSegCommand): like notsentLowatSockopt, must
+ // survive arriving before state exists (a sockopt sent before connect()/
+ // listen()). -1 = never set; otherwise clamps advertisedMss/snd_mss in
+ // configureStateVariables(), and applied directly to state when set later.
+ int userMss = -1;
+ // A route MTU handed down at runtime (TcpSetPathMtuCommand), same
+ // arrives-before-state discipline as userMss. 0 = never set; otherwise it
+ // overrides the pathMtu module param as the RFC 4821 search's ceiling.
+ int pathMtuSockopt = 0;
+ // A receive-buffer size handed down at runtime (TcpSetRcvBufCommand), same
+ // arrives-before-state discipline as userMss. -1 = never set; otherwise it
+ // overrides the receiveBufferSize module param AND pins the buffer.
+ int rcvBufSockopt = -1;
+ // Runtime TCP_NODELAY / TCP_CORK (TcpSetNoDelayCommand / TcpSetCorkCommand)
+ // that may arrive before OPEN creates state; INT_MIN = never set, otherwise
+ // applied in configureStateVariables() (mirrors notsentLowatSockopt/userMss).
+ int nodelaySockopt = INT_MIN;
+ int corkSockopt = INT_MIN;
bool autoRead = true;
+ // Linux sk->sk_socket presence: false = embryonic (listening-side, not yet
+ // accept()ed by the application). Gates OOO-pressure rcvbuf growth
+ // (tcp_data_queue_ofo's not-yet-accepted skip). Set via TcpSetOwnedCommand;
+ // defaults to owned so ordinary applications are unaffected.
+ bool appOwned = true;
+ // Linux SOCK_NOSPACE-while-waiting: the application's blocking write is
+ // stalled on send-buffer space (TcpSetWriterBlockedCommand). While set AND
+ // the send queue has run dry (everything queued is in flight), the
+ // SNDBUF_LIMITED chrono accumulates -- tcp_write_xmit's queue-empty +
+ // SOCK_NOSPACE start condition (tcp-info-sndbuf-limited).
+ bool writerBlocked = false;
bool peerClosedSentUp = false;
long maxByteCountRequested = 0; // from READ requests
@@ -145,6 +182,79 @@ class INET_API TcpConnection : public SimpleModule
TcpReceiveQueue *receiveQueue = nullptr;
TcpSackRexmitQueue *rexmitQueue = nullptr;
+ // MSG_EOR: sequence numbers marking the end of a SEND that
+ // requested a record boundary. sendSegment() must never build a segment
+ // spanning one of these; keyed on sequence number (not send-queue position)
+ // so it survives retransmission for free. Pruned lazily in sendSegment().
+ std::set eorSeqNums;
+ // per-write PSH boundaries (Linux tcp_mark_push at sendmsg time), consumed
+ // by sendSegment(); only populated under pushSegmentsOnWriteBoundary
+ std::set pushSeqNums;
+ // Linux forced_push boundaries (tcp_sendmsg copy loop): wire segments ending
+ // exactly here carry PSH; recorded at enqueue time, purged as they are acked
+ std::set forcedPushSeqNums;
+
+ // windowShrinkAllowed (Linux tcp_shrink_window=1) receive-buffer accounting:
+ // Linux charges the buffer at skb-TRUESIZE granularity and scales free space
+ // by the measured payload/truesize ratio (tp->scaling_ratio, a u8 fraction
+ // of 256 updated in tcp_measure_rcv_mss). Each accepted in-order data
+ // segment appends (payloadBytes, truesize) here; explicit application reads
+ // release entries front-to-back (an skb is freed only once fully copied).
+ std::deque> rcvSkbChain;
+ uint64_t rcvBufOccupancy = 0; // sum of truesizes in rcvSkbChain (Linux sk_rmem_alloc)
+ uint8_t rcvScalingRatio = 128; // Linux TCP_DEFAULT_SCALING_RATIO (50% of 256)
+ uint32_t rcvMssEstimate = 536; // Linux icsk_ack.rcv_mss seed; gates ratio updates
+
+ public:
+ // Set when a data segment reaching BEYOND the advertised-window promise
+ // (rcv_adv) was nevertheless accepted via the empty-receive-queue
+ // exception (tcp_sequence's over-accept): the kernel does NOT send an
+ // immediate ACK for such an arrival -- its own selftest documents this
+ // ("A too big packet is accepted if the receive queue is empty. It does
+ // not trigger an immediate ACK", tcp_rcv_neg_window; the 7.1.3 golden
+ // provably sends none, even though a static reading of the quickack path
+ // predicts one). Consumed and cleared by
+ // TcpAlgorithmBase::receiveSeqChanged's adaptive branch, which then takes
+ // the DELAYED path.
+ bool overWindowAcceptPending = false;
+
+ protected:
+
+ // Linux's skb truesize for a tun-received TCP segment: the payload plus
+ // IP+TCP headers and skb_shared_info (320B), allocated page-granular for
+ // large packets (tun_alloc_skb's paged path) or from a power-of-two page
+ // frag for small ones (tun_build_skb), plus struct sk_buff (~232B).
+ // Pins rcv_wnd_shrink_allowed's golden offers exactly (10000B payload ->
+ // 12520, 1023B -> 2280).
+ static uint32_t linuxSkbTruesize(uint32_t tcpPayloadBytes)
+ {
+ uint32_t base = tcpPayloadBytes + 40 + 320;
+ uint32_t alloc;
+ if (base > 16384)
+ // very large packets take order-3 (32KB) page compounds
+ // (alloc_skb_with_frags / PAGE_ALLOC_COSTLY_ORDER) -- two 39000/
+ // 60000-byte OOO injections then genuinely overflow a 131072
+ // rcvbuf (ooo-before-and-after-accept pins the resulting
+ // tcp_clamp_window growth)
+ alloc = ((base + 32767) / 32768) * 32768;
+ else if (base > 4096)
+ alloc = ((base + 4095) / 4096) * 4096;
+ else {
+ alloc = 1024;
+ while (alloc < base)
+ alloc <<= 1;
+ }
+ return alloc + 232;
+ }
+
+ // MSG_ZEROCOPY: pending completion notifications, keyed on the
+ // sequence number marking the end of a zerocopy-marked SEND's data -> the id to
+ // report once transmission (sendSegment() advancing snd_nxt past that seq)
+ // reaches it. IDs are assigned sequentially per connection, mirroring Linux's
+ // own SO_ZEROCOPY id assignment.
+ std::map zerocopySeqNums;
+ uint32_t nextZerocopyId = 0;
+
// TCP behavior in data transfer state
TcpAlgorithm *tcpAlgorithm = nullptr;
@@ -153,6 +263,7 @@ class INET_API TcpConnection : public SimpleModule
cMessage *connEstabTimer = nullptr;
cMessage *finWait2Timer = nullptr;
cMessage *synRexmitTimer = nullptr; // for retransmitting SYN and SYN+ACK
+ cMessage *rackReoTimer = nullptr; // RACK reordering timer (Linux ICSK_TIME_REO_TIMEOUT): fires when a not-yet-lost segment's RACK.rtt+reo_wnd deadline matures between ACKs
// statistics
long rcvdSegments = 0;
@@ -173,6 +284,29 @@ class INET_API TcpConnection : public SimpleModule
/** @name Processing app commands. Invoked from processAppCommand(). */
//@{
+ /**
+ * rackReoTimer expired: re-run RACK loss detection (time has advanced, so
+ * pending deadlines may have matured) and let the recovery strategy react
+ * (enter fast recovery / retransmit) via ITcpRecovery::reoTimeout().
+ */
+ virtual void processRackReoTimeout();
+
+ public:
+ /**
+ * (Re)arms the RACK reordering timer for the given delay, or cancels it when
+ * delay is negative. Called by the recovery strategy from RACK loss detection.
+ */
+ virtual void rescheduleRackReoTimer(simtime_t delay);
+
+ /**
+ * RFC 8985 section 7.2 loss probe: send one segment of new data if any is
+ * available, else retransmit the last outstanding segment. Returns whether
+ * anything was actually sent.
+ */
+ virtual bool sendTlpProbe();
+
+ protected:
+
virtual void process_OPEN_ACTIVE(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg);
virtual void process_OPEN_PASSIVE(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg);
virtual void process_ACCEPT(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg);
@@ -206,15 +340,31 @@ class INET_API TcpConnection : public SimpleModule
virtual bool processAckInEstabEtc(Packet *tcpSegment, const Ptr& tcpHeader);
//@}
+ /**
+ * AccECN: pick between the ACE field's packet-count-only naiveDelta
+ * and safeDelta candidates using the AccECN option's byte-exact CEB evidence
+ * as corroboration -- whichever candidate's byte estimate (delta * snd_mss)
+ * is closer to the observed cebByteDelta wins. Isolated as its own method so it's
+ * independently unit-testable (design reference: tcp_accecn_process's naive/safe/
+ * option-evidence *shape* only, tcp_input.c, re-derived not transcribed -- see the
+ * plan's Verified Facts point 3).
+ */
+ virtual int resolveAceDelta(int naiveDelta, int safeDelta, uint32_t cebByteDelta) const;
+
/** @name Processing of TCP options. Invoked from readHeaderOptions(). Return value indicates whether the option was valid. */
//@{
virtual bool processMSSOption(const Ptr& tcpHeader, const TcpOptionMaxSegmentSize& option);
virtual bool processWSOption(const Ptr& tcpHeader, const TcpOptionWindowScale& option);
virtual bool processSACKPermittedOption(const Ptr& tcpHeader, const TcpOptionSackPermitted& option);
- virtual bool processSACKOption(const Ptr& tcpHeader, const TcpOptionSack& option);
virtual bool processTSOption(const Ptr& tcpHeader, const TcpOptionTimestamp& option);
+ virtual bool processFastOpenOption(const Ptr& tcpHeader, const TcpOptionTcpFastOpen& option);
+ virtual bool processFastOpenExpOption(const Ptr& tcpHeader, const TcpOptionTcpFastOpenExp& option);
//@}
+ /** Shared cookie-processing core for both the standard (kind 34) and legacy
+ * experimental (kind 254 + 0xF989 magic) Fast Open options. */
+ virtual bool processFastOpenCookieBytes(const std::vector& cookie);
+
/** @name Processing timeouts. Invoked from processTimer(). */
//@{
virtual void process_TIMEOUT_2MSL();
@@ -252,9 +402,6 @@ class INET_API TcpConnection : public SimpleModule
/** Utility: writeHeaderOptions (Currently only EOL, NOP, MSS, WS, SACK_PERMITTED, SACK and TS are implemented) */
virtual TcpHeader writeHeaderOptions(const Ptr& tcpHeader);
- /** Utility: adds SACKs to segments header options field */
- virtual TcpHeader addSacks(const Ptr& tcpHeader);
-
/** Utility: get TSval from segments TS header option */
virtual uint32_t getTSval(const Ptr& tcpHeader) const;
@@ -273,12 +420,29 @@ class INET_API TcpConnection : public SimpleModule
*/
virtual bool sendData(uint32_t congestionWindow);
+ /** Utility: force out a partial segment currently withheld by TCP_CORK / MSG_MORE
+ * (uncork, TCP_NODELAY, or the cork timer). forcePush sets PSH on the flushed partial. */
+ virtual void flushCorkedData(bool forcePush);
+
/** Utility: sends 1 bytes as "probe", called by the "persist" mechanism */
virtual bool sendProbe();
+ /** Sends a zero-length keepalive probe (seq = snd_una - 1) to elicit an ACK from an idle peer. */
+ virtual void sendKeepAliveProbe();
+
/** Utility: retransmit one segment from snd_una */
virtual void retransmitOneSegment(bool called_at_rto);
+ /**
+ * RFC 5681 / Linux tcp_enter_loss: on an RTO, mark the un-SACKed outstanding
+ * data lost in the SACK scoreboard, so getBytesInFlight() stops counting it as
+ * in the network and the whole window can be clocked out on the recovering ACKs
+ * (rather than one segment per backed-off RTO on a tail drop with no SACK). No-op
+ * without SACK. Called by each flavour's RTO handler after it computes ssthresh
+ * from the pre-loss FlightSize.
+ */
+ virtual void markOutstandingLostOnRto();
+
/** Utility: retransmit all from snd_una to snd_max */
virtual void retransmitData();
@@ -299,9 +463,29 @@ class INET_API TcpConnection : public SimpleModule
*/
virtual uint32_t sendSegment(uint32_t bytes);
+ /**
+ * MSG_EOR: enqueues a SEND's data into sendQueue, and if the
+ * packet carries a TcpSendEorReq tag, records the new end of that data as a
+ * boundary sendSegment() must not build a segment across.
+ */
+ virtual void enqueueSendCommandData(Packet *packet);
+
/** Utility: adds control info to segment and sends it to IP */
virtual void sendToIP(Packet *tcpSegment, const Ptr& tcpHeader);
+ /**
+ * Utility: the AccECN ECN-field reflector encoding (RFC 9768 section 3.2.3.2).
+ * Maps the IP-ECN codepoint of the received SYN (reflected on the SYN-ACK) or
+ * SYN-ACK (reflected on the handshake-completing ACK) to the ACE value that
+ * carries it back: Not-ECT->0b010, ECT(1)->0b011, ECT(0)->0b100, CE->0b110.
+ * The gaps in the encoding are deliberate: 0b000/0b001/0b111 are reserved by
+ * table 2 for "no ECN" and "classic ECN only" during negotiation.
+ */
+ static uint8_t accEcnReflectedAce(int ipEcnCodepoint);
+
+ /** Utility: the IP-ECN codepoint a received segment arrived with, or IP_ECN_NOT_ECT if untagged */
+ static int receivedEcnCodepoint(Packet *tcpSegment);
+
/** Utility: start SYN-REXMIT timer */
virtual void startSynRexmitTimer();
@@ -343,6 +527,12 @@ class INET_API TcpConnection : public SimpleModule
/** Utility: update receiver queue related variables and statistics - called before setting rcv_wnd */
virtual void updateRcvQueueVars();
+ /** SNDBUF_LIMITED chrono sampling: (re)evaluate the "transmission starved
+ * by the send buffer" condition (writerBlocked && no unsent data && data
+ * outstanding) and open/close the accumulation interval accordingly.
+ * Called after sends, enqueues, the writer-blocked toggles and ACKs. */
+ virtual void updateSndbufLimitedChrono();
+
/** Utility: returns true when receive queue has enough space for store the tcpHeader */
virtual bool hasEnoughSpaceForSegmentInReceiveQueue(Packet *tcpSegment, const Ptr& tcpHeader);
@@ -354,6 +544,10 @@ class INET_API TcpConnection : public SimpleModule
/** Utility: update window information (snd_wnd, snd_wl1, snd_wl2) */
virtual void updateWndInfo(const Ptr& tcpHeader, bool doAlways = false);
+ std::string validationInfo() const;
+
+ virtual uint32_t calculateEffectiveMss();
+
public:
TcpConnection() {}
TcpConnection(const TcpConnection& other) {} // FIXME kludge
@@ -393,6 +587,65 @@ class INET_API TcpConnection : public SimpleModule
int getFsmState() const { return fsm.getState(); }
const TcpStateVariables *getState() const { return state; }
TcpStateVariables *getStateForUpdate() { return state; }
+
+ /**
+ * Maps INET's independent loss-recovery bools onto Linux's tcp_ca_state
+ * ordinals (TCP_CA_Open=0, Disorder=1, CWR=2, Recovery=3, Loss=4), for
+ * TcpStatusInfo::ca_state. INET has no state tracking Linux's Disorder --
+ * the pre-recovery "first dupACK seen" phase -- so that ordinal is never
+ * returned; scripts asserting ca_state==Disorder will still diverge. This
+ * is a known, documented imprecision, not a bug.
+ */
+ /**
+ * The sequence number that DATA accounting -- the peer's advertised window and
+ * the bytes-in-flight estimate -- is measured from. Normally snd_una, but a TCP
+ * Fast Open server transmitting its response from SYN_RCVD still has snd_una at
+ * the ISS: INET keeps the unacknowledged SYN-ACK there (the SYN-REXMIT timer owns
+ * that slot), while Linux's child socket starts at snd_una = ISN+1
+ * (tcp_create_openreq_child) and never counts the handshake segment as data --
+ * its packets_out only ever sees the write queue. Left uncorrected, the SYN-ACK's
+ * sequence slot eats one byte of both the peer's window and the congestion
+ * window, costing the response its last full segment.
+ */
+ virtual uint32_t getDataSndUna() const;
+
+ /**
+ * MTU <-> MSS conversions for the RFC 4821 search (Linux tcp_mtu_to_mss /
+ * tcp_mss_to_mtu). The header allowance is the FIXED part -- network header
+ * plus TCP header plus the options carried on every established segment --
+ * so the two are exact inverses and the search's bounds stay comparable.
+ */
+ /** Gives back the receive-buffer charge of the skbs an application read drained. */
+ virtual void releaseRcvBufOccupancy(uint64_t readBytes);
+
+ /**
+ * How many segments go out as one GSO super-segment. Only the wire-realism PSH
+ * rule depends on it: Linux forces PSH on a multi-segment skb, and after the
+ * split the flag lands on the burst's last slice.
+ */
+ virtual uint32_t gsoBurstSegments(uint32_t congestionWindow, uint32_t bytesInFlight, uint32_t effectiveMss) const;
+
+ virtual uint32_t mtuHeaderOverhead() const;
+ virtual uint32_t mtuToMss(uint32_t mtu) const;
+ virtual uint32_t mssToMtu(uint32_t mss) const;
+
+ /** Arms the RFC 4821 search bounds once the connection's MSS is settled (Linux tcp_mtup_init). */
+ virtual void mtupInit();
+
+ /**
+ * The payload of the RFC 4821 probe segment to send right now, or 0 when the
+ * conditions for probing are not met (Linux tcp_mtu_probe). The probe is
+ * deliberately larger than snd_mss: it is the experiment that decides whether
+ * the path carries a bigger segment.
+ */
+ virtual uint32_t mtuProbeBytes(uint32_t buffered, uint32_t congestionWindow) const;
+
+ /** The probe was acknowledged, so the path carries it: raise the lower bound and the MSS (Linux tcp_mtup_probe_success). */
+ virtual void mtupProbeSucceeded();
+
+ /** The probe was lost, so the path does not carry it: lower the upper bound (Linux tcp_mtup_probe_failed). */
+ virtual void mtupProbeFailed();
+ virtual int deriveLinuxCaState() const;
const TcpSendQueue *getSendQueue() const { return sendQueue; }
TcpSendQueue *getSendQueueForUpdate() { return sendQueue; }
const TcpSackRexmitQueue *getRexmitQueue() const { return rexmitQueue; }
@@ -468,46 +721,6 @@ class INET_API TcpConnection : public SimpleModule
*/
static bool isPacketTooBig(Icmpv6Type type, int code);
- /**
- * For SACK TCP. RFC 3517, page 3: "This routine returns whether the given
- * sequence number is considered to be lost. The routine returns true when
- * either DupThresh discontiguous SACKed sequences have arrived above
- * 'SeqNum' or (DupThresh * SMSS) bytes with sequence numbers greater
- * than 'SeqNum' have been SACKed. Otherwise, the routine returns
- * false."
- */
- virtual bool isLost(uint32_t seqNum);
-
- /**
- * For SACK TCP. RFC 3517, page 3: "This routine traverses the sequence
- * space from HighACK to HighData and MUST set the "pipe" variable to an
- * estimate of the number of octets that are currently in transit between
- * the TCP sender and the TCP receiver."
- */
- virtual void setPipe();
-
- /**
- * For SACK TCP. RFC 3517, page 3: "This routine uses the scoreboard data
- * structure maintained by the Update() function to determine what to transmit
- * based on the SACK information that has arrived from the data receiver
- * (and hence been marked in the scoreboard). NextSeg () MUST return the
- * sequence number range of the next segment that is to be
- * transmitted..."
- * Returns true if a valid sequence number (for the next segment) is found and
- * returns false if no segment should be send.
- */
- virtual bool nextSeg(uint32_t& seqNum);
-
- /**
- * Utility: send data during Loss Recovery phase (if SACK is enabled).
- */
- virtual void sendDataDuringLossRecoveryPhase(uint32_t congestionWindow);
-
- /**
- * Utility: send segment during Loss Recovery phase (if SACK is enabled).
- * Returns the number of bytes sent.
- */
- virtual uint32_t sendSegmentDuringLossRecoveryPhase(uint32_t seqNum);
/**
* Utility: send one new segment from snd_max if allowed (RFC 3042).
diff --git a/src/inet/transportlayer/tcp/TcpConnection.ned b/src/inet/transportlayer/tcp/TcpConnection.ned
index a90b8845b68..a70fbed3287 100644
--- a/src/inet/transportlayer/tcp/TcpConnection.ned
+++ b/src/inet/transportlayer/tcp/TcpConnection.ned
@@ -24,8 +24,9 @@ simple TcpConnection extends SimpleModule {
@signal[sndWnd]; // Snd_wnd
@signal[rcvWnd]; // Rcv_wnd
@signal[rcvAdv]; // Current advertised window (=rcv_adv)
- @signal[sndNxt]; // Sent seqNo
+ @signal[sndSeq]; // Sent seqNo
@signal[sndAck]; // Sent ackNo
+ @signal[sndMax]; // Snd_max
@signal[rcvSeq]; // Received seqNo
@signal[rcvAck]; // Received ackNo (=snd_una)
@signal[unacked]; // Number of bytes unacknowledged
@@ -36,9 +37,13 @@ simple TcpConnection extends SimpleModule {
@signal[rcvOooSeg]; // Number of received out-of-order segments
@signal[rcvNASeg]; // Number of received not acceptable segments
@signal[sackedBytes]; // Current number of received sacked bytes
+ @signal[delivered]; // Cumulative newly-delivered (acked + sacked) bytes (RFC 8985/6937)
+ @signal[deliveredCe]; // AccECN: cumulative resolved count of CE-marked packets the peer reported (draft-ietf-tcpm-accurate-ecn)
+ @signal[deliveredCeBytes]; // AccECN: cumulative CE byte count from AccECN option evidence
@signal[tcpRcvQueueBytes]; // Current amount of used bytes in TCP receive queue
@signal[tcpRcvQueueDrops]; // Number of drops in TCP receive queue
@signal[tcpRcvPayloadBytes]; // Amount of payload bytes received (including duplicates, out of order, etc.) for TCP throughput
+ @signal[bytesInFlight];
//TcpAlgorithm signals:
@signal[cwnd]; // Will record changes to snd_cwnd
@@ -57,8 +62,9 @@ simple TcpConnection extends SimpleModule {
@statistic[sndWnd](record=vector; interpolationmode=sample-hold); // Snd_wnd
@statistic[rcvWnd](record=vector; interpolationmode=sample-hold); // Rcv_wnd
@statistic[rcvAdv](record=vector; interpolationmode=sample-hold); // Current advertised window (=rcv_adv)
- @statistic[sndNxt](record=vector; interpolationmode=sample-hold); // Sent seqNo
+ @statistic[sndSeq](record=vector; interpolationmode=sample-hold); // Sent seqNo
@statistic[sndAck](record=vector; interpolationmode=sample-hold); // Sent ackNo
+ @statistic[sndMax](record=vector; interpolationmode=sample-hold); // Snd_max
@statistic[rcvSeq](record=vector; interpolationmode=sample-hold); // Received seqNo
@statistic[rcvAck](record=vector; interpolationmode=sample-hold); // Received ackNo (=snd_una)
@statistic[unacked](record=vector; interpolationmode=sample-hold); // Number of bytes unacknowledged
@@ -69,9 +75,13 @@ simple TcpConnection extends SimpleModule {
@statistic[rcvOooSeg](record=vector; interpolationmode=sample-hold); // Number of received out-of-order segments
@statistic[rcvNASeg](record=vector; interpolationmode=sample-hold); // Number of received not acceptable segments
@statistic[sackedBytes](record=vector; interpolationmode=sample-hold); // Current number of received sacked bytes
+ @statistic[delivered](record=vector; interpolationmode=sample-hold); // Cumulative newly-delivered (acked + sacked) bytes (RFC 8985/6937)
+ @statistic[deliveredCe](record=vector; interpolationmode=sample-hold); // AccECN: cumulative resolved count of CE-marked packets the peer reported
+ @statistic[deliveredCeBytes](record=vector; interpolationmode=sample-hold); // AccECN: cumulative CE byte count from AccECN option evidence
@statistic[tcpRcvQueueBytes](record=vector; interpolationmode=sample-hold); // Current amount of used bytes in tcp receive queue
@statistic[tcpRcvQueueDrops](record=vector; interpolationmode=sample-hold); // Number of drops in tcp receive queue
@statistic[tcpRcvPayloadBytes](record=vector; interpolationmode=sample-hold); // Current amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
+ @statistic[bytesInFlight](record=vector; interpolationmode=sample-hold);
@statistic[cwnd](record=vector; interpolationmode=sample-hold); // Will record changes to snd_cwnd
@statistic[ssthresh](record=vector; interpolationmode=sample-hold); // Will record changes to ssthresh
diff --git a/src/inet/transportlayer/tcp/TcpConnectionBase.cc b/src/inet/transportlayer/tcp/TcpConnectionBase.cc
index 45ceac933aa..3a493a462b4 100644
--- a/src/inet/transportlayer/tcp/TcpConnectionBase.cc
+++ b/src/inet/transportlayer/tcp/TcpConnectionBase.cc
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
//
+#include
#include
#include
@@ -13,35 +14,19 @@
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
#include "inet/transportlayer/tcp/TcpConnection.h"
#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
#include "inet/transportlayer/tcp/TcpSendQueue.h"
#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState_m.h"
namespace inet {
namespace tcp {
Define_Module(TcpConnection);
-simsignal_t TcpConnection::stateSignal = registerSignal("state"); // FSM state
-simsignal_t TcpConnection::sndWndSignal = registerSignal("sndWnd"); // snd_wnd
-simsignal_t TcpConnection::rcvWndSignal = registerSignal("rcvWnd"); // rcv_wnd
-simsignal_t TcpConnection::rcvAdvSignal = registerSignal("rcvAdv"); // current advertised window (=rcv_adv)
-simsignal_t TcpConnection::sndNxtSignal = registerSignal("sndNxt"); // sent seqNo
-simsignal_t TcpConnection::sndAckSignal = registerSignal("sndAck"); // sent ackNo
-simsignal_t TcpConnection::rcvSeqSignal = registerSignal("rcvSeq"); // received seqNo
-simsignal_t TcpConnection::rcvAckSignal = registerSignal("rcvAck"); // received ackNo (=snd_una)
-simsignal_t TcpConnection::unackedSignal = registerSignal("unacked"); // number of bytes unacknowledged
-simsignal_t TcpConnection::dupAcksSignal = registerSignal("dupAcks"); // current number of received dupAcks
-simsignal_t TcpConnection::pipeSignal = registerSignal("pipe"); // current sender's estimate of bytes outstanding in the network
-simsignal_t TcpConnection::sndSacksSignal = registerSignal("sndSacks"); // number of sent Sacks
-simsignal_t TcpConnection::rcvSacksSignal = registerSignal("rcvSacks"); // number of received Sacks
-simsignal_t TcpConnection::rcvOooSegSignal = registerSignal("rcvOooSeg"); // number of received out-of-order segments
-simsignal_t TcpConnection::rcvNASegSignal = registerSignal("rcvNASeg"); // number of received not acceptable segments
-simsignal_t TcpConnection::sackedBytesSignal = registerSignal("sackedBytes"); // current number of received sacked bytes
-simsignal_t TcpConnection::tcpRcvQueueBytesSignal = registerSignal("tcpRcvQueueBytes"); // current amount of used bytes in tcp receive queue
-simsignal_t TcpConnection::tcpRcvQueueDropsSignal = registerSignal("tcpRcvQueueDrops"); // number of drops in tcp receive queue
-simsignal_t TcpConnection::tcpRcvPayloadBytesSignal = registerSignal("tcpRcvPayloadBytes"); // amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
-
+simsignal_t TcpConnection::deliveredCeSignal = registerSignal("deliveredCe"); // AccECN: cumulative resolved count of CE-marked packets the peer has reported via the ACE field
+simsignal_t TcpConnection::deliveredCeBytesSignal = registerSignal("deliveredCeBytes"); // AccECN: cumulative CE byte count from AccECN option evidence only
TcpStateVariables::~TcpStateVariables()
{
}
@@ -101,6 +86,25 @@ void TcpConnection::initialize()
WATCH_EXPR("fsmState", stateName(fsm.getState()));
}
+std::string TcpConnection::validationInfo() const
+{
+ auto baseState = static_cast(state);
+ std::stringstream out;
+ out << "lostOut: " << rexmitQueue->getLost() << ", "
+ << "sackedOut: " << rexmitQueue->getSacked() << ", "
+ << "retrans: " << rexmitQueue->getRetrans() << ", "
+ << "bytesInFligh: " << tcpAlgorithm->getBytesInFlight() << ", "
+ << "ssthresh: " << static_cast(state)->ssthresh << ", "
+ << "cwnd: " << baseState->snd_cwnd << ", "
+ << "snd_una: " << state->snd_una << ", "
+ << "snd_max: " << state->snd_max << ", "
+ << "snd_wnd: " << state->snd_wnd << ", "
+ << "dup_ack: " << state->dupacks << ", "
+ << "recover: " << (baseState->recover != 0 ? baseState->recover + 1 : state->recoveryPoint) << ", "
+ << "recovery: " << (state->lossRecovery ? "true" : "false");
+ return out.str();
+}
+
//
// FSM framework, TCP FSM
//
@@ -121,10 +125,12 @@ void TcpConnection::initConnection(Tcp *_mod, int _socketId)
connEstabTimer = new cMessage("CONN-ESTAB");
finWait2Timer = new cMessage("FIN-WAIT-2");
synRexmitTimer = new cMessage("SYN-REXMIT");
+ rackReoTimer = new cMessage("RACK-REO");
the2MSLTimer->setContextPointer(this);
connEstabTimer->setContextPointer(this);
finWait2Timer->setContextPointer(this);
+ rackReoTimer->setContextPointer(this);
synRexmitTimer->setContextPointer(this);
WATCH(socketId);
@@ -171,6 +177,8 @@ TcpConnection::~TcpConnection()
delete cancelEvent(finWait2Timer);
if (synRexmitTimer)
delete cancelEvent(synRexmitTimer);
+ if (rackReoTimer)
+ delete cancelEvent(rackReoTimer);
}
void TcpConnection::handleMessage(cMessage *msg)
@@ -196,13 +204,34 @@ bool TcpConnection::processTimer(cMessage *msg)
process_TIMEOUT_2MSL();
}
else if (msg == connEstabTimer) {
- event = TCP_E_TIMEOUT_CONN_ESTAB;
- process_TIMEOUT_CONN_ESTAB();
+ if (state->fastopenSynDeferred) {
+ // TCP Fast Open (RFC 7413): the app called connect(fastOpen=true) with a
+ // cached cookie but never SEND-triggered the deferred SYN (misuse of the
+ // paired API, or a legitimately data-less Fast Open attempt) -- this is
+ // not a real connection-establishment timeout. Send the fallback bare SYN
+ // now (fastopenSynDataLen stays 0, so sendSyn() degrades to its ordinary
+ // bare-SYN behavior; the cookie is still attached, matching RFC 7413's
+ // explicitly-allowed data-less-SYN-with-valid-cookie case) and give the
+ // connection a fresh, full establishment window, instead of aborting it.
+ sendSyn();
+ state->fastopenSynDeferred = false;
+ startSynRexmitTimer();
+ scheduleAfter(TCP_TIMEOUT_CONN_ESTAB, connEstabTimer);
+ event = TCP_E_IGNORE;
+ }
+ else {
+ event = TCP_E_TIMEOUT_CONN_ESTAB;
+ process_TIMEOUT_CONN_ESTAB();
+ }
}
else if (msg == finWait2Timer) {
event = TCP_E_TIMEOUT_FIN_WAIT_2;
process_TIMEOUT_FIN_WAIT_2();
}
+ else if (msg == rackReoTimer) {
+ event = TCP_E_IGNORE;
+ processRackReoTimeout();
+ }
else if (msg == synRexmitTimer) {
event = TCP_E_IGNORE;
process_TIMEOUT_SYN_REXMIT(event);
@@ -220,6 +249,7 @@ bool TcpConnection::processTCPSegment(Packet *tcpSegment, const Ptractive ? TCP_S_CLOSED : TCP_S_LISTEN);
+ // Return-to-LISTEN is RFC 793's rule for a plain (single-
+ // connection) passive open only. A FORKED connection is a
+ // Linux child socket: it must die, or we would end up with
+ // TWO listeners (the resolved long-standing FIXME from
+ // processRstInSynReceived()). A TFO-ACCELERATED connection
+ // has app-visible state (accepted/possibly-delivered SYN
+ // data) -- Linux drives such a child to TCP_CLOSE
+ // (tcp_reset/tcp_done), it never silently re-listens.
+ FSM_Goto(fsm, (state->active || state->forked || state->fastopenAccelerated)
+ ? TCP_S_CLOSED : TCP_S_LISTEN);
break;
case TCP_E_RCV_ACK:
@@ -629,6 +671,45 @@ bool TcpConnection::performStateTransition(const TcpEventCode& event)
return fsm.getState() != TCP_S_CLOSED;
}
+void TcpConnection::rescheduleRackReoTimer(simtime_t delay)
+{
+ if (rackReoTimer == nullptr)
+ return;
+ if (rackReoTimer->isScheduled())
+ cancelEvent(rackReoTimer);
+ // Jiffy quantization (Linux tcp_rack_mark_lost: usecs_to_jiffies(timeout)+1):
+ // a positive sub-tick deadline fires whole ticks later on a real kernel --
+ // which is what lets a dupthresh-worth of further SACKs enter recovery
+ // inline before the timer, instead of the timer marking a burst's holes a
+ // fraction of a millisecond after the first SACK. The zero-delay act-now
+ // defer (ACK-path marks) is left untouched.
+ if (delay > SIMTIME_ZERO) {
+ simtime_t granularity = tcpMain->par("rackReoTimerGranularity");
+ if (granularity > SIMTIME_ZERO)
+ delay = (std::ceil(delay / granularity) + 1) * granularity;
+ }
+ if (delay >= SIMTIME_ZERO)
+ scheduleAfter(delay, rackReoTimer);
+}
+
+void TcpConnection::processRackReoTimeout()
+{
+ // The reordering-window deadline of some still-unacked segment matured with no
+ // ACK arriving to re-run detection -- re-run it now and let the recovery
+ // strategy act on any newly lost marks (enter recovery / retransmit). Guarded:
+ // the timer can be stale if the connection moved on (recovery completed and the
+ // queue drained, algorithm torn down mid-close).
+ if (!state || !state->sack_enabled || state->lossDetectionMode != 1
+ || rexmitQueue == nullptr || tcpAlgorithm == nullptr)
+ return;
+ auto recovery = dynamic_cast(tcpAlgorithm->getRecovery());
+ if (recovery == nullptr)
+ return;
+ recovery->rackDetectAndMarkLost(/*fromReoTimer=*/true);
+ if (rexmitQueue->getLost() > 0)
+ recovery->reoTimeout();
+}
+
void TcpConnection::stateEntered(int state, int oldState, TcpEventCode event)
{
// cancel timers
@@ -642,6 +723,12 @@ void TcpConnection::stateEntered(int state, int oldState, TcpEventCode event)
ASSERT(connEstabTimer && synRexmitTimer);
cancelEvent(connEstabTimer);
cancelEvent(synRexmitTimer);
+ // A TCP Fast Open server may have sent response data from
+ // SYN_RCVD, arming the data-transfer timers (REXMIT etc.);
+ // the embryonic connection's send state is abandoned wholesale,
+ // so those timers must not survive the fall back to LISTEN (a
+ // late REXMIT firing in LISTEN walked freshly-reset queues).
+ tcpAlgorithm->connectionClosed();
break;
case TCP_S_SYN_RCVD:
@@ -653,6 +740,9 @@ void TcpConnection::stateEntered(int state, int oldState, TcpEventCode event)
delete cancelEvent(connEstabTimer);
delete cancelEvent(synRexmitTimer);
connEstabTimer = synRexmitTimer = nullptr;
+ // The MSS is settled only now (both SYNs seen), which is where Linux
+ // arms the RFC 4821 search too (tcp_init_transfer -> tcp_mtup_init).
+ mtupInit();
// TCP_I_ESTAB notification moved inside event processing
break;
diff --git a/src/inet/transportlayer/tcp/TcpConnectionEventProc.cc b/src/inet/transportlayer/tcp/TcpConnectionEventProc.cc
index 3ed68bcc613..fa31db1c3a4 100644
--- a/src/inet/transportlayer/tcp/TcpConnectionEventProc.cc
+++ b/src/inet/transportlayer/tcp/TcpConnectionEventProc.cc
@@ -5,16 +5,22 @@
//
+#include
#include
#include "inet/common/socket/SocketTag_m.h"
#include "inet/transportlayer/contract/tcp/TcpCommand_m.h"
+#include "inet/transportlayer/tcp_common/TcpHeader.h"
#include "inet/transportlayer/tcp/Tcp.h"
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
#include "inet/transportlayer/tcp/TcpConnection.h"
#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
+#include "inet/transportlayer/contract/tcp/TcpTimestampingTag_m.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
#include "inet/transportlayer/tcp/TcpSendQueue.h"
-#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBaseState_m.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState_m.h"
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
namespace inet {
namespace tcp {
@@ -53,6 +59,38 @@ void TcpConnection::process_OPEN_ACTIVE(TcpEventCode& event, TcpCommand *tcpComm
tcpMain->addSockPair(this, localAddr, remoteAddr, localPort, remotePort);
+ // TCP Fast Open (RFC 7413): if the app asked for it and a cookie is
+ // already cached for this destination, defer the SYN until the app's
+ // first SEND arrives (process_SEND fills in the data and calls
+ // sendSyn()) instead of sending a bare SYN now. If no cookie is
+ // cached, send an immediate (dataless) SYN that just requests one --
+ // FSM_Goto(TCP_S_SYN_SENT) below doesn't depend on sendSyn() having
+ // actually been called, so deferring here is FSM-transition-transparent.
+ if (openCmd->getFastOpen() && state->fastopenClientEnabled) {
+ state->fastopenRequested = true;
+ std::vector cachedCookie;
+ // isActiveFastOpenDisabled(): active blackhole detection tripped --
+ // treat exactly like "no cookie cached" (still request one via a
+ // bare, dataless SYN), so the connection proceeds normally, just
+ // without the data-attached acceleration until the timeout elapses.
+ // Cookie-less client mode (Linux tcp_fastopen bit 0x4,
+ // TFO_CLIENT_NO_COOKIE): defer and attach data exactly like a
+ // cache hit, but with no cookie to put in the SYN -- the FO
+ // option is absent entirely (writeHeaderOptions has neither a
+ // cached cookie nor a pending request to emit).
+ if (!tcpMain->isActiveFastOpenDisabled()
+ && (tcpMain->getFastOpenCookie(remoteAddr, cachedCookie)
+ || tcpMain->par("fastopenClientNoCookieRequired").boolValue())) {
+ selectInitialSeqNum();
+ state->fastopenSynDeferred = true;
+ scheduleAfter(TCP_TIMEOUT_CONN_ESTAB, connEstabTimer);
+ delete openCmd;
+ delete msg;
+ return;
+ }
+ state->fastopenCookieRequestPending = true;
+ }
+
// send initial SYN
selectInitialSeqNum();
sendSyn();
@@ -113,8 +151,9 @@ void TcpConnection::process_ACCEPT(TcpEventCode& event, TcpCommand *tcpCommand,
void TcpConnection::process_SEND(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg)
{
- // FIXME how to support PUSH? One option is to treat each SEND as a unit of data,
- // and set PSH at SEND boundaries
+ // PSH-at-record-boundary is opt-in per SEND via the packet's TcpSendEorReq tag
+ // (MSG_EOR) rather than automatic on every SEND -- see
+ // enqueueSendCommandData() and sendSegment()'s PSH-bit logic.
Packet *packet = check_and_cast(msg);
switch (fsm.getState()) {
case TCP_S_INIT:
@@ -127,20 +166,71 @@ void TcpConnection::process_SEND(TcpEventCode& event, TcpCommand *tcpCommand, cM
sendSyn();
startSynRexmitTimer();
scheduleAfter(TCP_TIMEOUT_CONN_ESTAB, connEstabTimer);
- sendQueue->enqueueAppData(packet); // queue up for later
+ enqueueSendCommandData(packet); // queue up for later
EV_DETAIL << sendQueue->getBytesAvailable(state->snd_una) << " bytes in queue\n";
break;
case TCP_S_SYN_RCVD:
+ enqueueSendCommandData(packet);
+ if (state->fastopenAccelerated) {
+ // TCP Fast Open server (RFC 7413 section 4.2): the connection was
+ // created from a SYN whose data was accepted, so the app already
+ // read that data and may respond BEFORE the handshake-completing
+ // ACK arrives -- the response transmits from SYN_RCVD (this is
+ // TFO's data-exchange-during-handshake acceleration; a regular
+ // SYN_RCVD connection keeps queueing until ESTABLISHED).
+ EV_DETAIL << "Fast Open: sending response data during SYN_RCVD\n";
+ tcpAlgorithm->sendCommandInvoked();
+ }
+ else {
+ EV_DETAIL << "Queueing up data for sending later.\n";
+ EV_DETAIL << sendQueue->getBytesAvailable(state->snd_una) << " bytes in queue\n";
+ }
+ break;
+
case TCP_S_SYN_SENT:
+ if (state->fastopenSynDeferred) {
+ // TCP Fast Open (RFC 7413): this is the SEND process_OPEN_ACTIVE
+ // deferred the SYN for. Attach as much of it as fits in one
+ // segment and send the data-bearing SYN now.
+ // A DATALESS SEND is legal here: sendto(..., 0, MSG_FASTOPEN)
+ // with a cached cookie still releases the deferred SYN (Linux
+ // sends the bare cookie-bearing SYN inside that syscall) --
+ // nothing to queue, availableBytes stays 0 below.
+ if (packet->getByteLength() > 0)
+ enqueueSendCommandData(packet);
+ else
+ delete packet;
+ uint32_t availableBytes = sendQueue->getBytesAvailable(state->iss + 1);
+ // SYN-payload cap: the peer's MSS cached with the cookie minus
+ // the maximum TCP option space (40) -- Linux sizes the SYN data
+ // from the tcp_metrics-cached MSS, since nothing has been
+ // negotiated yet on this connection.
+ uint32_t capBytes = state->snd_mss > 0 ? state->snd_mss : 536;
+ uint32_t cachedMss = tcpMain->getFastOpenCachedMss(remoteAddr);
+ if (cachedMss > 40)
+ capBytes = cachedMss - 40;
+ else if (capBytes > 40)
+ capBytes -= 40;
+ state->fastopenSynDataLen = availableBytes < capBytes ? availableBytes : capBytes;
+ // fastopenSynDeferred stays true through sendSyn() itself: it doubles
+ // as writeHeaderOptions()'s signal that this is a first-ever SYN being
+ // sent from SYN_SENT (not the usual TCP_S_INIT), so the SYN gets its
+ // full option set despite syn_rexmit_count still being 0.
+ sendSyn();
+ state->fastopenSynDeferred = false;
+ startSynRexmitTimer();
+ // connEstabTimer was already scheduled from process_OPEN_ACTIVE.
+ break;
+ }
EV_DETAIL << "Queueing up data for sending later.\n";
- sendQueue->enqueueAppData(packet); // queue up for later
+ enqueueSendCommandData(packet); // queue up for later
EV_DETAIL << sendQueue->getBytesAvailable(state->snd_una) << " bytes in queue\n";
break;
case TCP_S_ESTABLISHED:
case TCP_S_CLOSE_WAIT:
- sendQueue->enqueueAppData(packet);
+ enqueueSendCommandData(packet);
EV_DETAIL << sendQueue->getBytesAvailable(state->snd_una) << " bytes in queue, plus "
<< (state->snd_max - state->snd_una) << " bytes unacknowledged\n";
tcpAlgorithm->sendCommandInvoked();
@@ -156,6 +246,13 @@ void TcpConnection::process_SEND(TcpEventCode& event, TcpCommand *tcpCommand, cM
if ((state->sendQueueLimit > 0) && (sendQueue->getBytesAvailable(state->snd_una) > state->sendQueueLimit))
state->queueUpdate = false;
+
+ // TCP_NOTSENT_LOWAT: arm re-notification once the not-yet-
+ // transmitted portion of the queue (from snd_nxt, not snd_una -- independent of
+ // sendQueueLimit above) exceeds the low-water mark; sendSegment() disarms it and
+ // signals the app again once transmission brings it back down to/below the mark.
+ if (state->notsentLowat != (uint32_t)-1 && sendQueue->getBytesAvailable(state->snd_nxt) > state->notsentLowat)
+ state->notsentLowatUpdate = false;
}
void TcpConnection::process_READ_REQUEST(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg)
@@ -178,10 +275,24 @@ void TcpConnection::process_READ_REQUEST(TcpEventCode& event, TcpCommand *tcpCom
if (seqLess(requestedEndPos, endSeqNo))
endSeqNo = requestedEndPos;
if (Packet *dataMsg = receiveQueue->extractBytesUpTo(endSeqNo)) {
+ releaseRcvBufOccupancy(dataMsg->getByteLength());
dataMsg->setKind(TCP_I_DATA);
dataMsg->addTag()->setSocketId(socketId);
+ if (rxTimestampingEnabled)
+ dataMsg->addTag();
sendToApp(dataMsg);
maxByteCountRequested = 0;
+ // Linux tcp_cleanup_rbuf: a read that frees a lot of buffer is worth an
+ // immediate window update. Without it a peer that ran into a window
+ // closed by unread data has no way to learn the application caught up,
+ // short of a zero-window probe. "A lot" is the reference's test: the
+ // window the freed space now allows is at least twice what is still open.
+ if (state->rcvBufferSize > 0 && rcvBufOccupancy < state->rcvBufferSize) {
+ uint64_t freeSpace = ((uint64_t)(state->rcvBufferSize - rcvBufOccupancy) * rcvScalingRatio) >> 8;
+ if (freeSpace > 0 && freeSpace >= 2ULL * state->rcv_wnd
+ && 2ULL * state->rcv_wnd <= state->window_clamp)
+ sendAck();
+ }
}
}
if (!peerClosedSentUp && fsm.getState() == TCP_S_CLOSE_WAIT && this->receiveQueue->getQueueLength() == 0) {
@@ -203,6 +314,96 @@ void TcpConnection::process_OPTIONS(TcpEventCode& event, TcpCommand *tcpCommand,
else if (auto cmd = dynamic_cast(tcpCommand)) {
dscp = cmd->getDscp();
}
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ rxTimestampingEnabled = cmd->getEnabled();
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // Runtime TCP_NOTSENT_LOWAT: same field the notsentLowat module param
+ // seeds at connection setup (configureStateVariables); -1 disables.
+ // May legally arrive before OPEN creates state (like setTimestamping
+ // above) -- keep the value on the connection and apply it now only if
+ // state already exists; configureStateVariables() applies it otherwise.
+ notsentLowatSockopt = cmd->getValue();
+ if (state != nullptr)
+ state->notsentLowat = (notsentLowatSockopt < 0) ? (uint32_t)-1 : (uint32_t)notsentLowatSockopt;
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // Runtime TCP_MAXSEG: clamp the advertised and effective send MSS. Like
+ // TCP_NOTSENT_LOWAT above it may arrive before OPEN creates state; keep it
+ // on the connection and let configureStateVariables() apply it, or apply
+ // now if state already exists (a mid-connection clamp).
+ userMss = cmd->getValue();
+ if (state != nullptr && userMss > 0) {
+ state->advertisedMss = userMss;
+ if (state->snd_mss == (uint32_t)-1 || (uint32_t)userMss < state->snd_mss)
+ state->snd_mss = userMss;
+ state->snd_effmss = calculateEffectiveMss();
+ }
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // SO_RCVBUF. Nothing already queued is discarded -- Linux only stops
+ // ACCEPTING once the buffer is over budget -- but the buffer is now pinned,
+ // so the growth that normally absorbs pressure is off the table.
+ rcvBufSockopt = cmd->getValue();
+ if (state != nullptr && rcvBufSockopt >= 0) {
+ state->rcvBufferSize = (uint32_t)rcvBufSockopt;
+ state->rcvbufLocked = true;
+ EV_DETAIL << "Receive buffer pinned at " << state->rcvBufferSize << " bytes\n";
+ }
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // A route change under an open connection. Linux notices it in
+ // tcp_current_mss (dst_mtu != icsk_pmtu_cookie) -- but with MTU probing
+ // armed the MSS still cannot exceed what the search has proven, so the new
+ // ceiling only takes effect through a successful probe.
+ pathMtuSockopt = cmd->getValue();
+ if (state != nullptr && pathMtuSockopt > 0) {
+ state->pathMtu = (uint32_t)pathMtuSockopt;
+ if (state->mtupEnabled && state->mtupSearchHigh < state->pathMtu)
+ state->mtupSearchHigh = state->pathMtu;
+ EV_DETAIL << "Path MTU is now " << state->pathMtu << "\n";
+ }
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // The application's blocking write is (no longer) stalled on
+ // send-buffer space -- drives the SNDBUF_LIMITED chrono
+ // (tcp-info-sndbuf-limited).
+ writerBlocked = cmd->getBlocked();
+ EV_DETAIL << "Application writer is " << (writerBlocked ? "blocked on send-buffer space" : "no longer blocked") << "\n";
+ updateSndbufLimitedChrono();
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // Application-ownership marker (Linux sk->sk_socket): gates the
+ // kernel behaviors that skip embryonic (not-yet-accepted) sockets,
+ // e.g. OOO-pressure rcvbuf growth (ooo-before-and-after-accept).
+ appOwned = cmd->getOwned();
+ EV_DETAIL << "Connection ownership set to " << (appOwned ? "owned" : "embryonic") << "\n";
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // Runtime TCP_NODELAY: nagle_enabled is the runtime Nagle switch (nodelay
+ // disables Nagle). Enabling nodelay force-flushes any withheld partial
+ // (Linux __tcp_push_pending_frames on the nagle-off transition) but does NOT
+ // clear tcp_cork -- CORK outranks NODELAY for future small writes. May arrive
+ // before OPEN creates state; stash and let configureStateVariables() apply it.
+ nodelaySockopt = cmd->getNodelay() ? 1 : 0;
+ if (state != nullptr) {
+ state->nagle_enabled = !cmd->getNodelay();
+ if (cmd->getNodelay())
+ flushCorkedData(false);
+ }
+ }
+ else if (auto cmd = dynamic_cast(tcpCommand)) {
+ // Runtime TCP_CORK: persistently hold the trailing sub-MSS partial. A
+ // true->false transition (uncork) force-flushes the withheld partial
+ // (Linux tcp_uncork tail). May arrive before OPEN creates state.
+ corkSockopt = cmd->getCork() ? 1 : 0;
+ if (state != nullptr) {
+ bool wasCorked = state->tcp_cork;
+ state->tcp_cork = cmd->getCork();
+ if (wasCorked && !cmd->getCork())
+ flushCorkedData(false);
+ }
+ }
else
throw cRuntimeError("Unknown subclass of TcpSetOptionCommand received from app: %s", tcpCommand->getClassName());
delete tcpCommand;
@@ -211,6 +412,12 @@ void TcpConnection::process_OPTIONS(TcpEventCode& event, TcpCommand *tcpCommand,
void TcpConnection::process_CLOSE(TcpEventCode& event, TcpCommand *tcpCommand, cMessage *msg)
{
+ // full close vs shutdown(SHUT_WR): a full close also shuts the receive side
+ // down (see rcvShutdown's comment); a half close keeps reading possible.
+ // state is still null for a CLOSE that reaches a freshly created (INIT)
+ // connection -- e.g. an app closing an fd whose connect attempt failed.
+ if (state != nullptr && (tcpCommand == nullptr || !tcpCommand->getHalfClose()))
+ state->rcvShutdown = true;
delete tcpCommand;
delete msg;
@@ -239,8 +446,19 @@ void TcpConnection::process_CLOSE(TcpEventCode& event, TcpCommand *tcpCommand, c
EV_DETAIL << "No outstanding SENDs, sending FIN right away, advancing snd_nxt over the FIN\n";
state->snd_nxt = state->snd_max;
sendFin();
- tcpAlgorithm->restartRexmitTimer();
state->snd_max = ++state->snd_nxt;
+ emit(sndMaxSignal, state->snd_max);
+ // The FIN is new data on the wire: arm the retransmit timer if
+ // it is not already running for outstanding data, and (re)arm
+ // the loss probe for the new tail of the flight -- Linux
+ // tcp_event_new_data_sent + tcp_schedule_loss_probe; a plain
+ // rexmit-timer restart would disarm a pending probe (shared
+ // timer slot) and the lost FIN would wait out the full RTO.
+ // Must run AFTER snd_max advances over the FIN: schedulePto()
+ // refuses to arm while snd_una == snd_max, which silently
+ // skipped the probe for a FIN-only close (nothing else in
+ // flight -- user_timeout pins TLP-then-backoff FIN rexmits).
+ tcpAlgorithm->dataSent(state->snd_max - 1);
emit(unackedSignal, state->snd_max - state->snd_una);
@@ -260,7 +478,7 @@ void TcpConnection::process_CLOSE(TcpEventCode& event, TcpCommand *tcpCommand, c
case TCP_S_CLOSING:
case TCP_S_LAST_ACK:
case TCP_S_TIME_WAIT:
- // RFC 793 is not entirely clear on how to handle a duplicate close request.
+ // RFC 9293 is not entirely clear on how to handle a duplicate close request.
// Here we treat it as an error.
throw cRuntimeError(tcpMain, "Duplicate CLOSE command: connection already closing");
}
@@ -286,11 +504,10 @@ void TcpConnection::process_ABORT(TcpEventCode& event, TcpCommand *tcpCommand, c
case TCP_S_FIN_WAIT_2:
case TCP_S_CLOSE_WAIT:
//"
- // Send a reset segment:
- //
- //
- //"
- sendRst(state->snd_nxt);
+ // Send a reset segment. RFC 793 shows a bare ,
+ // but Linux's active reset (tcp_send_active_reset, also the
+ // tcp_disconnect/AF_UNSPEC path) always sends RST|ACK with ack=rcv_nxt.
+ sendRstAck(state->snd_nxt, state->rcv_nxt, localAddr, remoteAddr, localPort, remotePort);
break;
}
}
@@ -306,8 +523,35 @@ void TcpConnection::process_STATUS(TcpEventCode& event, TcpCommand *tcpCommand,
{
delete tcpCommand; // but reuse msg for reply
- if (fsm.getState() == TCP_S_INIT)
- throw cRuntimeError("Error processing command STATUS: connection not open");
+ if (fsm.getState() == TCP_S_INIT) {
+ // Linux parity: getsockopt(TCP_INFO) works on ANY socket fd, including
+ // one whose connection attempt was refused/reset/timed out and whose
+ // PCB is already gone -- the fd simply reports TCP_CLOSE. An app
+ // STATUS landing here means Tcp created this fresh connection for the
+ // command because the original was torn down: report a closed socket
+ // (default/zeroed fields are the honest values) instead of crashing.
+ TcpStatusInfo *closedInfo = new TcpStatusInfo();
+ closedInfo->setState(TCP_S_CLOSED);
+ closedInfo->setStateName(stateName(TCP_S_CLOSED));
+ closedInfo->setLocalAddr(localAddr);
+ closedInfo->setRemoteAddr(remoteAddr);
+ closedInfo->setLocalPort(localPort);
+ closedInfo->setRemotePort(remotePort);
+ closedInfo->setCwnd(UINT_MAX);
+ closedInfo->setSrtt(-1);
+ closedInfo->setRexmitCount(UINT_MAX);
+ closedInfo->setNumRtos(UINT_MAX);
+ closedInfo->setSsthresh(UINT_MAX);
+ closedInfo->setLost(UINT_MAX);
+ closedInfo->setRetrans(UINT_MAX);
+ closedInfo->setBackoff(UINT_MAX);
+ closedInfo->setProbes(UINT_MAX);
+ msg->setControlInfo(closedInfo);
+ msg->setKind(TCP_I_STATUS);
+ check_and_cast(msg)->addTag()->setSocketId(socketId);
+ sendToApp(msg);
+ return;
+ }
TcpStatusInfo *statusInfo = new TcpStatusInfo();
@@ -321,6 +565,8 @@ void TcpConnection::process_STATUS(TcpEventCode& event, TcpCommand *tcpCommand,
statusInfo->setAutoRead(autoRead);
statusInfo->setSnd_mss(state->snd_mss);
+ statusInfo->setSndEffMss(state->snd_effmss);
+ statusInfo->setAdvmss(state->advertisedMss);
statusInfo->setSnd_una(state->snd_una);
statusInfo->setSnd_nxt(state->snd_nxt);
statusInfo->setSnd_max(state->snd_max);
@@ -335,8 +581,105 @@ void TcpConnection::process_STATUS(TcpEventCode& event, TcpCommand *tcpCommand,
statusInfo->setIrs(state->irs);
statusInfo->setFin_ack_rcvd(state->fin_ack_rcvd);
+ // Adaptive reordering (RFC 4653-style dynamic DupThresh): state->reordering
+ // grows past the static dupthresh as checkSackReordering() observes SACKs
+ // arriving below the FACK (Linux tp->reordering). Report the live degree, not
+ // the static dupthresh -- tcpi_reordering must track the adaptive value.
+ statusInfo->setReordering(state->reordering);
+ statusInfo->setMinRtt(state->minRtt.dbl());
+ statusInfo->setFlightSize(tcpAlgorithm->getBytesInFlight());
+ statusInfo->setSackedBytes(state->sackedBytes);
+ statusInfo->setDeliveredBytes(state->deliveredBytes);
+ statusInfo->setTsEnabled(state->ts_enabled);
+ statusInfo->setSackEnabled(state->sack_enabled);
+ statusInfo->setWsEnabled(state->ws_enabled);
+ statusInfo->setEctEnabled(state->ect);
+ statusInfo->setSynDataAccepted(state->fastopenSynDataAccepted);
+ statusInfo->setSndWndScale(state->snd_wnd_scale);
+ statusInfo->setLastDataRecvTime(state->time_last_segment_received);
+
+ // Congestion-window/RTO/RTT fields live on flavour-specific state variable
+ // subclasses, one or two levels below the base TcpStateVariables* held as
+ // `state` -- not every flavour (e.g. DumbTcp) has them, so guard with a
+ // dynamic_cast and fall back to the UINT_MAX sentinel documented on
+ // TcpStatusInfo.
+ if (auto *baseAlgState = dynamic_cast(state)) {
+ statusInfo->setCwnd(baseAlgState->snd_cwnd);
+ statusInfo->setSrtt(baseAlgState->srtt.dbl());
+ statusInfo->setRexmitCount(baseAlgState->rexmit_count);
+ statusInfo->setNumRtos(baseAlgState->numRtos);
+ }
+ else {
+ statusInfo->setCwnd(UINT_MAX);
+ statusInfo->setSrtt(-1);
+ statusInfo->setRexmitCount(UINT_MAX);
+ statusInfo->setNumRtos(UINT_MAX);
+ }
+
+ if (auto *tahoeRenoState = dynamic_cast(state))
+ statusInfo->setSsthresh(tahoeRenoState->ssthresh);
+ else
+ statusInfo->setSsthresh(UINT_MAX);
+
+ statusInfo->setCaState(deriveLinuxCaState());
+ // rcv_nxt/irs are only meaningful once the 3WHS has fixed irs (peer's ISN); before
+ // that (e.g. a STATUS query in SYN_SENT) both are still 0 and the subtraction
+ // would underflow.
+ statusInfo->setBytesReceived(seqGreater(state->rcv_nxt, state->irs) ? state->rcv_nxt - state->irs - 1 : 0);
+ statusInfo->setDeliveredCePkts(state->deliveredCePkts);
+ statusInfo->setDeliveredCeBytes(state->deliveredCeBytes);
+ statusInfo->setDeliveredE0Bytes(state->deliveredE0Bytes);
+ statusInfo->setDeliveredE1Bytes(state->deliveredE1Bytes);
+
+ // TCP_INFO trio: report the accumulated total plus, if a period is still open
+ // right now, the elapsed time since it started -- so a live query reflects the
+ // up-to-the-moment total rather than only the last fully-closed period.
+ statusInfo->setBusyTime((state->busyTimeAccumulated
+ + (state->busyStartTime >= SIMTIME_ZERO ? simTime() - state->busyStartTime : SIMTIME_ZERO)).dbl());
+ statusInfo->setRwndLimited((state->rwndLimitedAccumulated
+ + (state->rwndLimitedStartTime >= SIMTIME_ZERO ? simTime() - state->rwndLimitedStartTime : SIMTIME_ZERO)).dbl());
+ statusInfo->setSndbufLimited((state->sndbufLimitedAccumulated
+ + (state->sndbufLimitedStartTime >= SIMTIME_ZERO ? simTime() - state->sndbufLimitedStartTime : SIMTIME_ZERO)).dbl());
+
+ // Segment counts are approximated from byte totals by rounding UP: Linux
+ // counts skbs, and a single retransmitted/lost sub-MSS segment (e.g. the
+ // 1420-byte TFO fallback rexmit against a 1460 MSS) must report 1, not 0
+ // (syn-data-only-syn-acked asserts tcpi_retrans == 1).
+ if (state->sack_enabled && rexmitQueue != nullptr && state->snd_mss > 0)
+ statusInfo->setLost((rexmitQueue->getLost() + state->snd_mss - 1) / state->snd_mss);
+ else
+ statusInfo->setLost(UINT_MAX);
+
+ if (state->sack_enabled && rexmitQueue != nullptr && state->snd_mss > 0)
+ statusInfo->setRetrans((rexmitQueue->getRetrans() + state->snd_mss - 1) / state->snd_mss);
+ else
+ statusInfo->setRetrans(UINT_MAX);
+
+ // Linux SK_MEMINFO_RCVBUF: the live sk_rcvbuf -- receiveBufferSize as
+ // configured, possibly grown by tcp_clamp_window under OOO pressure
+ // (ooo-before-and-after-accept asserts both the untouched embryonic value
+ // and the post-accept growth). 0 when no buffer size is configured.
+ statusInfo->setSkRcvbuf(state->rcvBufferSize);
+
+ if (auto *baseAlgState = dynamic_cast(state)) {
+ statusInfo->setBackoff(baseAlgState->rexmit_count);
+ statusInfo->setProbes(baseAlgState->zeroWindowProbesSent);
+ }
+ else {
+ statusInfo->setBackoff(UINT_MAX);
+ statusInfo->setProbes(UINT_MAX);
+ }
+
msg->setControlInfo(statusInfo);
msg->setKind(TCP_I_STATUS);
+ // Every other reply-sending path in this file tags its outgoing message
+ // with SocketInd (see sendIndicationToApp() and friends in
+ // TcpConnectionUtil.cc) so TcpSocket::belongsToSocket() can match it back
+ // to the requesting app-side socket. This path reuses the incoming
+ // request message, which only ever carried a SocketReq tag -- without
+ // this, the STATUS reply is silently dropped by the app's socket
+ // dispatch instead of reaching TcpSocket::ICallback::socketStatusArrived().
+ check_and_cast(msg)->addTag()->setSocketId(socketId);
sendToApp(msg);
}
diff --git a/src/inet/transportlayer/tcp/TcpConnectionRcvSegment.cc b/src/inet/transportlayer/tcp/TcpConnectionRcvSegment.cc
index 338bb07f461..34bd0d3e4c3 100644
--- a/src/inet/transportlayer/tcp/TcpConnectionRcvSegment.cc
+++ b/src/inet/transportlayer/tcp/TcpConnectionRcvSegment.cc
@@ -7,14 +7,16 @@
#include
+#include "inet/networklayer/common/EcnTag_m.h"
#include "inet/transportlayer/contract/tcp/TcpCommand_m.h"
+#include "inet/transportlayer/tcp_common/TcpHeader.h"
#include "inet/transportlayer/tcp/Tcp.h"
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
#include "inet/transportlayer/tcp/TcpConnection.h"
#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
#include "inet/transportlayer/tcp/TcpSendQueue.h"
-#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
namespace inet {
namespace tcp {
@@ -37,7 +39,7 @@ void TcpConnection::segmentArrivalWhileClosed(Packet *tcpSegment, const Ptrstr() << "\n";
+ state->time_last_segment_received = simTime(); // idle base for keepalive
+
+ // snapshot delivered-bytes so consumers can read this segment's newly
+ // acked+sacked bytes as deliveredBytes - prrDeliveredMark (RFC 6937 PRR input,
+ // also used by AccECN to approximate this ACK's delivered packet count)
+ state->prrDeliveredMark = state->deliveredBytes;
+
+ // reset the per-segment D-SACK detection (RFC 2883 loss undo)
+ state->dsackSeen = false;
+ state->dsackBytes = 0;
+
emit(rcvSeqSignal, tcpHeader->getSequenceNo());
emit(rcvAckSignal, tcpHeader->getAckNo());
emit(tcpRcvPayloadBytesSignal, int(tcpSegment->getByteLength() - tcpHeader->getHeaderLength().get()));
//
- // Note: this code is organized exactly as RFC 793, section "3.9 Event
- // Processing", subsection "SEGMENT ARRIVES".
+ // Note: this code is organized exactly as
+ // RFC 9293, section "3.10 Event Processing", subsection "3.10.7. SEGMENT ARRIVES".
//
TcpEventCode event;
@@ -98,7 +111,7 @@ TcpEventCode TcpConnection::process_RCV_SEGMENT(Packet *tcpSegment, const PtrgetByteLength() - tcpHeader->getHeaderLength().get();
uint32_t payloadSeq = tcpHeader->getSequenceNo();
uint32_t firstSeq = receiveQueue->getFirstSeqNo();
+ // Linux tcp_try_rmem_schedule on a buffer the application pinned with SO_RCVBUF.
+ // tcp_can_ingest asks only whether the socket is ALREADY over budget, so the
+ // segment that first crosses the line still gets in and the next one does not.
+ // Normally tcp_prune_queue would recover by growing the buffer (tcp_clamp_window),
+ // but a locked buffer forbids that and there is nothing else left to reclaim, so
+ // the answer is a hard drop until the application reads. Deliberately narrower
+ // than the reference: an unlocked buffer keeps INET's grow-on-pressure path below.
+ if (payloadLength > 0 && state->rcvbufLocked && state->rcvBufferSize > 0
+ && rcvBufOccupancy > state->rcvBufferSize)
+ return false;
if (seqLess(payloadSeq, firstSeq)) {
long delta = firstSeq - payloadSeq;
payloadSeq += delta;
payloadLength -= delta;
}
- return seqLE(firstSeq, payloadSeq) && seqLE(payloadSeq + payloadLength, firstSeq + state->maxRcvBuffer);
+ // Linux exception (tcp_data_queue): an in-order segment arriving to an
+ // EMPTY receive queue is accepted even beyond the buffer limit -- the
+ // advertised window then collapses toward 0 until the application drains
+ // it. A hard drop would force the peer to retransmit forever against a
+ // receiver that has room the moment its app reads (rcv_zero_wnd_fin pins
+ // 'ack 60001 win 0' for a single 60000B segment against SO_RCVBUF 20000).
+ if (payloadLength > 0 && payloadSeq == state->rcv_nxt
+ && receiveQueue->getAcknowledgedDataLength() == 0
+ && receiveQueue->getAmountOfBufferedBytes() == 0)
+ return true;
+ // buffer CAPACITY, not the advertised window: rcvBufferSize (Linux
+ // sk_rcvbuf, e.g. tcp_rmem[1]) when configured, else the historical
+ // maxRcvBuffer conflation
+ uint32_t bufferCap = state->rcvBufferSize > 0 ? std::max(state->rcvBufferSize, state->maxRcvBuffer) : state->maxRcvBuffer;
+ return seqLE(firstSeq, payloadSeq) && seqLE(payloadSeq + payloadLength, firstSeq + bufferCap);
}
TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const Ptr& tcpHeader)
@@ -129,7 +166,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
tcpAlgorithm->processEcnInEstablished();
//
- // RFC 793: first check sequence number
+ // RFC 9293: "First, check sequence number"
//
bool acceptable = true;
@@ -169,10 +206,88 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
else {
if (tcpHeader->getSynBit()) {
EV_DETAIL << "SYN with unacceptable seqNum in " << stateName(fsm.getState()) << " state received (SYN duplicat?)\n";
+ // AccECN reflector, duplicate-SYN-ACK arm: the ACK we are about to send
+ // in response reflects THIS SYN-ACK's IP-ECN codepoint, exactly like the
+ // handshake-completing ACK did for the original (RFC 9768 section
+ // 3.2.3.2 -- the reflection answers a SYN-ACK, so every answer to one
+ // carries it, not just the first). accecn 3rd_ack_after_synack_rxmt,
+ // synack_rexmit and no_ecn_after_accecn pin the three shapes: a
+ // differently-marked duplicate, an identical one, and one that dropped
+ // its ACE bits altogether.
+ if (state->accEcnNegotiated && tcpHeader->getAckBit()) {
+ state->accEcnReflectCodepoint = receivedEcnCodepoint(tcpSegment);
+ state->accEcnReflectAce = true;
+ }
+ // Only a PURE SYN retransmit earns the SYN-ACK resend (Linux
+ // tcp_check_req's request-socket path). A SYN+ACK here is the
+ // peer completing a SIMULTANEOUS open -- it takes the normal
+ // dup-segment route below: a D-SACK-bearing plain ACK, never a
+ // SYN-ACK retransmit (simultaneous-fast-open pins
+ // ". 1001:1001(0) ack 1 ").
+ if (fsm.getState() == TCP_S_SYN_RCVD && !tcpHeader->getAckBit()) {
+ // A retransmitted SYN while we sit in SYN_RCVD means our
+ // SYN-ACK was lost: Linux re-sends the SYN-ACK (from the
+ // ORIGINAL negotiation -- tcp_check_req/TCP_SYN_RECV
+ // resend path), not a plain ACK. The AccECN
+ // accecn_then_notecn_syn / notecn_then_accecn_syn pair
+ // pins both directions: renegotiating from the new SYN's
+ // (possibly different) ECN codepoint would be wrong, so
+ // the stored negotiation state is reused as-is.
+ EV_DETAIL << "Re-sending SYN-ACK for the retransmitted SYN\n";
+ // count it as a SYN-ACK retransmission: the rexmit-gated
+ // option rules apply (e.g. Linux omits the AccECN option
+ // on SYN-ACK retransmits -- the *_drop/_rxmt scripts and
+ // accecn_then_notecn_syn pin this)
+ state->syn_rexmit_count++;
+ // The negotiation is reused as-is, but the ECN-field REFLECTION is
+ // per-packet: the resent SYN-ACK reflects the codepoint of the SYN
+ // that triggered it, which need not be the one the first SYN carried
+ // (accecn_then_notecn_syn: an [ect0] SYN then a [noecn] retransmit,
+ // answered SA. and then SW.).
+ if (state->accEcnNegotiated)
+ state->accEcnReflectCodepoint = receivedEcnCodepoint(tcpSegment);
+ sendSynAck();
+ // AccECN downgrade (accecn_then_notecn_syn): a peer whose
+ // RETRANSMITTED SYN carries no ACE bits abandoned its ECN
+ // request (likely blackholed) -- stop setting ECT on our
+ // packets from here on (Linux ACE_FAIL handling), while
+ // the ACE-field/option feedback machinery keeps running.
+ // AFTER sendSynAck(): it re-derives ect from the stored
+ // negotiation and would undo the downgrade.
+ if (state->accEcnNegotiated && !tcpHeader->getAeBit()
+ && !tcpHeader->getEceBit() && !tcpHeader->getCwrBit() && state->ect)
+ {
+ EV_DETAIL << "Retransmitted SYN lost its ACE bits: disabling ECT marking\n";
+ state->ect = false;
+ }
+ state->rcv_naseg++;
+ emit(rcvNASegSignal, state->rcv_naseg);
+ return TCP_E_IGNORE;
+ }
}
- else if (payloadLength > 0 && state->sack_enabled && seqLess((tcpHeader->getSequenceNo() + payloadLength), state->rcv_nxt)) {
+ else if (payloadLength + tcpHeader->getSynFinLen() > 0 && state->sack_enabled
+ && seqLess(tcpHeader->getSequenceNo(), state->rcv_nxt)) {
+ // Linux tcp_send_dupack: ANY old data (seq before rcv_nxt) earns a
+ // D-SACK, including a duplicate ending exactly at rcv_nxt -- the
+ // range's right edge is capped at rcv_nxt by addSacks. SEG.LEN
+ // counts SYN and FIN (Linux end_seq): a duplicate SYN-ACK's
+ // one-sequence-number SYN gets D-SACKed as [irs, irs+1)
+ // (simultaneous-fast-open's "sack 0:1").
+ //
+ // Linux tcp_rcv_spurious_retrans, AccECN arm: our previous ACK for
+ // this very duplicate carried both the AccECN option and its D-SACK,
+ // yet the same segment arrives yet again -- a middlebox is evidently
+ // dropping our option-bearing ACKs, so stop sending the option for
+ // the rest of the connection (kernel:tcp_accecn_client_accecn_options_drop).
+ if (state->accEcnNegotiated && state->accEcnOptSentWithDsack
+ && tcpHeader->getSequenceNo() == state->accEcnSentDsackStart
+ && !state->accEcnOptFailSend)
+ {
+ EV_DETAIL << "AccECN option + D-SACK ACK was evidently lost twice: disabling AccECN option emission\n";
+ state->accEcnOptFailSend = true;
+ }
state->start_seqno = tcpHeader->getSequenceNo();
- state->end_seqno = tcpHeader->getSequenceNo() + payloadLength;
+ state->end_seqno = tcpHeader->getSequenceNo() + payloadLength + tcpHeader->getSynFinLen();
state->snd_dsack = true;
EV_DETAIL << "SND_D-SACK SET (dupseg rcvd)\n";
}
@@ -201,7 +316,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
//
- // RFC 793: second check the RST bit,
+ // RFC 9293: "Second, check the RST bit"
//
if (tcpHeader->getRstBit()) {
// Note: if we come from LISTEN, processSegmentInListen() has already handled RST.
@@ -212,11 +327,10 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// came from the LISTEN state), then return this connection to
// LISTEN state and return. The user need not be informed. If
// this connection was initiated with an active OPEN (i.e., came
- // from SYN-SENT state) then the connection was refused, signal
- // the user "connection refused". In either case, all segments
- // on the retransmission queue should be removed. And in the
- // active OPEN case, enter the CLOSED state and delete the TCB,
- // and return.
+ // from SYN-SENT state) then the connection was refused; signal
+ // the user "connection refused". In either case, the retransmission
+ // queue should be flushed. And in the active OPEN case, enter
+ // the CLOSED state and delete the TCB, and return.
//"
return processRstInSynReceived(tcpHeader);
@@ -251,11 +365,11 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
}
- // RFC 793: third check security and precedence
+ // RFC 9293: Third, check security
// This step is ignored.
//
- // RFC 793: fourth, check the SYN bit,
+ // RFC 9293: Fourth, check the SYN bit
//
if (tcpHeader->getSynBit()
&& !(fsm.getState() == TCP_S_SYN_RCVD && tcpHeader->getAckBit())) {
@@ -279,7 +393,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
//
- // RFC 793: fifth check the ACK field,
+ // RFC 9293: Fifth, check the ACK field
//
if (!tcpHeader->getAckBit()) {
// if the ACK bit is off drop the segment and return
@@ -294,7 +408,11 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
if (fsm.getState() == TCP_S_SYN_RCVD) {
//"
// If SND.UNA =< SEG.ACK =< SND.NXT then enter ESTABLISHED state
- // and continue processing.
+ // and continue processing with the variables below set to:
+ //
+ // SND.WND <- SEG.WND
+ // SND.WL1 <- SEG.SEQ
+ // SND.WL2 <- SEG.ACK
//
// If the segment acknowledgment is not acceptable, form a
// reset segment,
@@ -308,6 +426,16 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
return TCP_E_IGNORE;
}
+ state->snd_effmss = calculateEffectiveMss();
+
+ // Seed the RTT estimator from the handshake RTT (Linux measures the
+ // SYN<->SYN-ACK exchange via tcp_ack_update_rtt/tcp_synack_rtt_meas and
+ // enters ESTABLISHED with srtt/rttvar -- and hence the first RTO -- already
+ // RTT-scaled instead of the initial default). Karn: skipped if our handshake
+ // segment was retransmitted.
+ if (state->seedRttFromHandshake && state->syn_rexmit_count == 0 && state->handshakeSentTime >= SIMTIME_ZERO)
+ tcpAlgorithm->rttMeasurementComplete(state->handshakeSentTime, simTime());
+
// notify tcpAlgorithm and app layer
tcpAlgorithm->established(false);
@@ -316,6 +444,20 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
else
sendEstabIndicationToApp();
+ // Simultaneous open completed by a duplicate SYN-ACK: its SYN occupies
+ // an already-received sequence number, and Linux's tcp_data_queue
+ // D-SACKs that one-sequence-number range with an immediate ACK right
+ // after establishing (simultaneous-fast-open pins
+ // ". 1001:1001(0) ack 1 ").
+ if (tcpHeader->getSynBit() && state->sack_enabled && state->dsack_enabled
+ && seqLess(tcpHeader->getSequenceNo(), state->rcv_nxt)) {
+ state->start_seqno = tcpHeader->getSequenceNo();
+ state->end_seqno = tcpHeader->getSequenceNo() + 1;
+ state->snd_dsack = true;
+ state->ack_now = true;
+ sendAck();
+ }
+
// This will trigger transition to ESTABLISHED. Timers and notifying
// app will be taken care of in stateEntered().
event = TCP_E_RCV_ACK;
@@ -323,7 +465,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
uint32_t old_snd_nxt = state->snd_nxt; // later we'll need to see if snd_nxt changed
// Note: If one of the last data segments is lost while already in LAST-ACK state (e.g. if using TCPEchoApps)
- // TCP must be able to process acceptable acknowledgments, however please note RFC 793, page 73:
+ // TCP must be able to process acceptable acknowledgments, however please note RFC 9293:
// "LAST-ACK STATE
// The only thing that can arrive in this state is an
// acknowledgment of our FIN. If our FIN is now acknowledged,
@@ -337,16 +479,16 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// ESTABLISHED processing:
//"
// If SND.UNA < SEG.ACK =< SND.NXT then, set SND.UNA <- SEG.ACK.
- // Any segments on the retransmission queue which are thereby
+ // Any segments on the retransmission queue that are thereby
// entirely acknowledged are removed. Users should receive
- // positive acknowledgments for buffers which have been SENT and
+ // positive acknowledgments for buffers that have been SENT and
// fully acknowledged (i.e., SEND buffer should be returned with
// "ok" response). If the ACK is a duplicate
- // (SEG.ACK < SND.UNA), it can be ignored. If the ACK acks
+ // (SEG.ACK =< SND.UNA), it can be ignored. If the ACK acks
// something not yet sent (SEG.ACK > SND.NXT) then send an ACK,
// drop the segment, and return.
//
- // If SND.UNA < SEG.ACK =< SND.NXT, the send window should be
+ // If SND.UNA =< SEG.ACK =< SND.NXT, the send window should be
// updated. If (SND.WL1 < SEG.SEQ or (SND.WL1 = SEG.SEQ and
// SND.WL2 =< SEG.ACK)), set SND.WND <- SEG.WND, set
// SND.WL1 <- SEG.SEQ, and set SND.WL2 <- SEG.ACK.
@@ -367,8 +509,8 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
//"
// FIN-WAIT-1 STATE
// In addition to the processing for the ESTABLISHED state, if
- // our FIN is now acknowledged then enter FIN-WAIT-2 and continue
- // processing in that state.
+ // the FIN segment is now acknowledged then enter FIN-WAIT-2
+ // continue processing in that state.
//"
event = TCP_E_RCV_ACK; // will trigger transition to FIN-WAIT-2
}
@@ -425,7 +567,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
//
- // RFC 793: sixth, check the URG bit,
+ // RFC 9293: Sixth, check the URG bit
//
if (tcpHeader->getUrgBit() && (fsm.getState() == TCP_S_ESTABLISHED ||
fsm.getState() == TCP_S_FIN_WAIT_1 || fsm.getState() == TCP_S_FIN_WAIT_2))
@@ -443,30 +585,57 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
//
- // RFC 793: seventh, process the segment text,
+ // RFC 9293: Seventh, process the segment text
//
uint32_t old_rcv_nxt = state->rcv_nxt; // if rcv_nxt changes, we need to send/schedule an ACK
+ // D-SACK bookkeeping (RFC 2883): first already-buffered range duplicated by
+ // this segment, captured just before the insert merges the regions.
+ bool dupRangeFound = false;
+ uint32_t dupStart = 0, dupEnd = 0;
+
+ // RFC 1122 4.2.2.13 (Linux TCPABORTONDATA, tcp_rcv_state_process): the
+ // application has fully CLOSEd -- it will never read again -- so NEW data
+ // arriving in FIN_WAIT_1/2 would be silently discarded while the peer
+ // believes it was delivered. RFC 793 said queue it; RFC 1122 (and BSD,
+ // and Linux) say reset the connection instead. Only after a FULL close
+ // (rcvShutdown): a shutdown(SHUT_WR) half close keeps receiving legal.
+ // The FIN bit itself is not data for this test, and the RST is the
+ // reset-REPLY form (Linux returns 1 from tcp_rcv_state_process and
+ // tcp_v4_send_reset stamps the RST from the offending segment's ack
+ // field, not from snd_nxt -- user_timeout pins 'R ').
+ if ((fsm.getState() == TCP_S_FIN_WAIT_1 || fsm.getState() == TCP_S_FIN_WAIT_2)
+ && state->rcvShutdown && payloadLength > 0
+ && seqGreater(tcpHeader->getSequenceNo() + payloadLength, state->rcv_nxt))
+ {
+ EV_INFO << "New data after full CLOSE (application gone) -- resetting the connection (RFC 1122 4.2.2.13)\n";
+ sendRst(tcpHeader->getAckNo());
+ return TCP_E_ABORT;
+ }
if (fsm.getState() == TCP_S_SYN_RCVD || fsm.getState() == TCP_S_ESTABLISHED ||
fsm.getState() == TCP_S_FIN_WAIT_1 || fsm.getState() == TCP_S_FIN_WAIT_2)
{
//"
// Once in the ESTABLISHED state, it is possible to deliver segment
- // text to user RECEIVE buffers. Text from segments can be moved
+ // data to user RECEIVE buffers. Data from segments can be moved
// into buffers until either the buffer is full or the segment is
- // empty. If the segment empties and carries an PUSH flag, then
+ // empty. If the segment empties and carries a PUSH flag, then
// the user is informed, when the buffer is returned, that a PUSH
// has been received.
//
// When the TCP takes responsibility for delivering the data to the
- // user it must also acknowledge the receipt of the data.
+ // user, it must also acknowledge the receipt of the data.
//
- // Once the TCP takes responsibility for the data it advances
+ // Once the TCP takes responsibility for the data, it advances
// RCV.NXT over the data accepted, and adjusts RCV.WND as
// appropriate to the current buffer availability. The total of
// RCV.NXT and RCV.WND should not be reduced.
//
- // Please note the window management suggestions in section 3.7.
+ // A TCP implementation MAY send an ACK segment acknowledging
+ // RCV.NXT when a valid segment arrives that is in the window but
+ // not at the left window edge (MAY-13).
+
+ // Please note the window management suggestions in section 3.8.
//
// Send an acknowledgment of the form:
//
@@ -476,7 +645,14 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// transmitted if possible without incurring undue delay.
//"
- if (payloadLength > 0) {
+ // Only deliver segment text that actually extends the window. A segment whose
+ // data lies ENTIRELY at/below RCV.NXT (all duplicate) has nothing to insert --
+ // it reaches here only when it also carries a FIN at the window edge, which
+ // isSegmentAcceptable() now accepts (tcp_close_no_rst). Feeding its fully-
+ // duplicate payload to insertBytesFromSegment() extracts a zero-length chunk
+ // and aborts ("Returning an empty chunk is not allowed"); the FIN is still
+ // processed by the "Eighth, check the FIN bit" step below.
+ if (payloadLength > 0 && seqGreater((uint32_t)tcpHeader->getSequenceNo() + (uint32_t)payloadLength, state->rcv_nxt)) {
// check for full sized segment
if ((uint32_t)payloadLength == state->snd_mss || (uint32_t)payloadLength + (tcpHeader->getHeaderLength() - TCP_MIN_HEADER_LENGTH).get() == state->snd_mss)
state->full_sized_segment_counter++;
@@ -497,18 +673,75 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// section 2.5).
uint32_t old_usedRcvBuffer = state->usedRcvBuffer;
+ // D-SACK (RFC 2883): find the first already-buffered range this
+ // segment duplicates BEFORE the insert merges the regions (Linux
+ // reports it via tcp_dsack_set/tcp_dsack_extend as tcp_ofo_queue
+ // drains the out-of-order queue over a gap-filling segment).
+ if (state->sack_enabled && state->dsack_enabled && payloadLength > 0)
+ dupRangeFound = receiveQueue->findFirstDuplicateRange(tcpHeader->getSequenceNo(),
+ tcpHeader->getSequenceNo() + payloadLength, dupStart, dupEnd);
state->rcv_nxt = receiveQueue->insertBytesFromSegment(tcpSegment, tcpHeader);
- if (seqGreater(state->snd_una, old_snd_una)) {
- // notify
- tcpAlgorithm->receivedDataAck(old_snd_una);
-
- // in the receivedDataAck we need the old value
- state->dupacks = 0;
+ // Receive-buffer occupancy at Linux's skb-truesize granularity,
+ // plus the measured payload/truesize scaling ratio
+ // (tcp_measure_rcv_mss: updated when a segment at least as
+ // large as the current rcv_mss estimate arrives with a
+ // DIFFERENT length; the estimate itself follows
+ // min(len, advmss)). Consumed by the windowShrinkAllowed offer
+ // arithmetic and by the tcp_clamp_window growth below.
+ if (payloadLength > 0) {
+ // Over-accept detection: this segment's end lies BEYOND the
+ // highest window edge ever promised (rcv_mwnd_seq, still the
+ // pre-arrival value here) yet it was accepted -- the
+ // empty-queue exception. The kernel does not immediate-ACK
+ // such an arrival (rcv_neg_window); receiveSeqChanged
+ // consumes the flag and takes the delayed path.
+ if (seqGreater(tcpHeader->getSequenceNo() + payloadLength, state->rcv_mwnd_seq))
+ overWindowAcceptPending = true;
+ uint32_t truesize = linuxSkbTruesize(payloadLength);
+ rcvSkbChain.push_back(std::make_pair(payloadLength, truesize));
+ rcvBufOccupancy += truesize;
+ if (payloadLength >= rcvMssEstimate) {
+ if (payloadLength != rcvMssEstimate) {
+ uint64_t ratio = ((uint64_t)payloadLength << 8) / truesize;
+ rcvScalingRatio = (uint8_t)std::min(ratio ? ratio : 1, 255);
+ }
+ rcvMssEstimate = std::min((uint32_t)payloadLength,
+ state->advertisedMss > 0 ? (uint32_t)state->advertisedMss : (uint32_t)payloadLength);
+ }
+ // Linux tcp_clamp_window: when the queued skbs' truesize
+ // outgrows sk_rcvbuf on a socket the application OWNS
+ // (accepted or actively opened), the buffer itself is
+ // grown toward tcp_rmem[2] rather than dropping -- an
+ // EMBRYONIC (not-yet-accepted) connection keeps its
+ // initial tcp_rmem[1] buffer untouched
+ // (ooo-before-and-after-accept pins both halves).
+ if (appOwned && !isToBeAccepted() && state->rcvBufferSize > 0
+ && rcvBufOccupancy > state->rcvBufferSize)
+ {
+ EV_DETAIL << "Receive-buffer pressure (occupancy " << rcvBufOccupancy
+ << " > rcvbuf " << state->rcvBufferSize
+ << "): growing sk_rcvbuf (tcp_clamp_window)\n";
+ state->rcvBufferSize = (uint32_t)std::min(rcvBufOccupancy, UINT32_MAX);
+ if (state->maxRcvBuffer < state->rcvBufferSize)
+ state->maxRcvBuffer = state->rcvBufferSize;
+ }
+ }
- emit(dupAcksSignal, state->dupacks);
+ // receiver window auto-tuning (Linux tcp_grow_window, called
+ // for both in-order and out-of-order arrivals): grow the offer
+ // by max(2*advmss, 2*len) toward the clamp -- a single large
+ // segment can open most of the remaining room at once
+ // (incr = max_t(int, incr, 2 * skb->len) in the reference).
+ if (state->rcv_ssthresh > 0 && state->rcv_ssthresh < state->window_clamp && payloadLength > 0) {
+ uint32_t incr = std::max((uint32_t)(2 * state->snd_mss), (uint32_t)(2 * payloadLength));
+ uint32_t room = state->window_clamp - state->rcv_ssthresh;
+ state->rcv_ssthresh += std::min(room, incr);
}
+ if (seqGreater(state->snd_una, old_snd_una))
+ tcpAlgorithm->receivedAckForUnackedData(old_snd_una);
+
// out-of-order segment?
if (old_rcv_nxt == state->rcv_nxt) {
state->rcv_oooseg++;
@@ -599,13 +832,20 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// if the ACK bit is off drop the segment and return
EV_WARN << "RcvQueueBuffer has run out, dropping segment\n";
+ // Linux answers a dropped-for-memory arrival immediately and with a
+ // zero window (ICSK_ACK_NOMEM | ICSK_ACK_NOW), so the peer learns at
+ // once that retransmitting is pointless until the application reads.
+ if (state->rcvbufLocked) {
+ state->ackNomem = true;
+ sendAck();
+ }
return TCP_E_IGNORE;
}
}
}
//
- // RFC 793: eighth, check the FIN bit,
+ // RFC 9293: Eighth, check the FIN bit
//
if (tcpHeader->getFinBit()) {
state->ack_now = true;
@@ -618,7 +858,7 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// user.
//"
- // Note: seems like RFC 793 is not entirely correct here: if the
+ // Note: seems like RFC 9293 is not entirely correct here: if the
// segment is "above sequence" (ie. RCV.NXT < SEG.SEQ), we cannot
// advance RCV.NXT over the FIN. Instead we remember this sequence
// number and do it later.
@@ -678,7 +918,19 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
// received a FIN that needs to be acked (or both), we need to send or
// schedule an ACK.
if (state->sack_enabled) {
- if (receiveQueue->getQueueLength() != 0) {
+ if (dupRangeFound) {
+ // RFC 2883: a gap-filling (or partially duplicate) segment covered
+ // data that was already buffered -- report the first duplicated
+ // range as a D-SACK block; addSacks() appends the still-missing
+ // out-of-order blocks (if any) after it (Linux tcp_ofo_queue ->
+ // tcp_dsack_extend).
+ state->start_seqno = dupStart;
+ state->end_seqno = dupEnd;
+ state->snd_dsack = true;
+ EV_DETAIL << "SND_D-SACK SET (segment duplicates buffered range [" << dupStart << ".." << dupEnd << "))\n";
+ state->ack_now = true;
+ }
+ else if (receiveQueue->getQueueLength() != 0) {
// RFC 2018, page 4:
// "If sent at all, SACK options SHOULD be included in all ACKs which do
// not ACK the highest sequence number in the data receiver's queue."
@@ -690,6 +942,15 @@ TcpEventCode TcpConnection::processSegment1stThru8th(Packet *tcpSegment, const P
}
}
+ // RFC 5681, page 8:
+ // "3.2 Fast Retransmit/Fast Recovery
+ // (...)
+ // In addition, a TCP receiver SHOULD send an immediate ACK
+ // when the incoming segment fills in all or part of a gap in the
+ // sequence space."
+ if (tcpHeader->getSequenceNo() + payloadLength != state->rcv_nxt)
+ state->ack_now = true; // although not mentioned in [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 861] seems like we have to set ack_now
+
// tcpAlgorithm decides when and how to do ACKs
tcpAlgorithm->receiveSeqChanged();
}
@@ -810,6 +1071,7 @@ TcpEventCode TcpConnection::processSynInListen(Packet *tcpSegment, const Ptrrcv_nxt = tcpHeader->getSequenceNo() + 1;
state->rcv_adv = state->rcv_nxt + state->rcv_wnd;
+ state->rcv_mwnd_seq = state->rcv_adv;
emit(rcvAdvSignal, state->rcv_adv);
@@ -817,21 +1079,101 @@ TcpEventCode TcpConnection::processSynInListen(Packet *tcpSegment, const Ptrinit(state->rcv_nxt); // FIXME may init twice...
selectInitialSeqNum();
- // although not mentioned in RFC 793, seems like we have to pick up
+ // although not mentioned in RFC 9293, seems like we have to pick up
// initial snd_wnd from the segment here.
updateWndInfo(tcpHeader, true);
if (tcpHeader->getHeaderLength() > TCP_MIN_HEADER_LENGTH) // Header options present?
readHeaderOptions(tcpHeader);
+ // Linux tcp_syncookies=2 (always-on cookies): the connection is rebuilt
+ // from the cookie at the handshake ACK, and its 2-bit MSS field quantizes
+ // the peer's advertised MSS DOWN to the IPv4 msstab (net/ipv4/syncookies.c
+ // msstab[] = {536, 1300, 1440, 1460}: largest entry not above the
+ // advertised value). wscale/SACK/TS ride the timestamp encoding and stay
+ // exact (syncookies_ip4_9k pins snd_mss 1448 = 1460 - 12 against an
+ // advertised 8960).
+ if (tcpMain->par("syncookiesAlways").boolValue() && state->snd_mss > 536) {
+ static const uint32_t msstab[] = { 536, 1300, 1440, 1460 };
+ uint32_t clamped = msstab[0];
+ for (uint32_t entry : msstab)
+ if (entry <= state->snd_mss)
+ clamped = entry;
+ if (clamped < state->snd_mss) {
+ EV_DETAIL << "syncookies=2: peer MSS " << state->snd_mss << " quantized to msstab " << clamped << "\n";
+ state->snd_mss = clamped;
+ state->snd_effmss = calculateEffectiveMss();
+ }
+ }
+
state->ack_now = true;
- // ECN
- if (tcpHeader->getEceBit() == true && tcpHeader->getCwrBit() == true) {
+ // ECN. AccECN's request codepoint (SEWA: ECE=CWR=AE=1) is checked first -- it's a
+ // superset of the classic-ECN-willing bit pattern (ECE=CWR=1), so without this check
+ // first an AccECN SYN would also satisfy the classic branch below and get misread as a
+ // plain RFC 3168 request.
+ if (tcpHeader->getEceBit() && tcpHeader->getCwrBit() && tcpHeader->getAeBit()
+ && (state->ecnMode == TCP_ECN_MODE_ACCECN || state->ecnMode == TCP_ECN_MODE_ACCECN_PASSIVE))
+ {
+ state->endPointIsWillingECN = true;
+ state->accEcnNegotiated = true;
+ // The SYN-ACK about to go out reflects this SYN's IP-ECN codepoint in its ACE
+ // field (RFC 9768 section 3.2.3.2) -- capture it here, the last point at which
+ // the IP layer's EcnInd tag is still attached to the segment.
+ state->accEcnReflectCodepoint = receivedEcnCodepoint(tcpSegment);
+ EV << "AccECN-setup SYN received (IP-ECN " << state->accEcnReflectCodepoint << ")\n";
+ }
+ else if (tcpHeader->getEceBit() == true && tcpHeader->getCwrBit() == true
+ && (!tcpHeader->getAeBit() || state->ecnMode == TCP_ECN_MODE_RFC3168)) {
+ // Classic branch. An RFC3168-mode host ACCEPTS an AE-carrying SYN as
+ // ECN-willing -- Linux tcp_ecn_create_request's condition
+ // (!ect || th->res1 || th->ae) && ecn_ok treats the AE bit as evidence
+ // FOR a compliant peer (RFC 8311 section 4.3 allows future extensions
+ // on that bit), so an AccECN SYN falls back to plain RFC 3168 here
+ // (accecn_to_rfc3168 pins the SE. SYN-ACK and ect0-marked data). A
+ // PASSIVE-mode host still requires AE=0: it never volunteered for ECN
+ // and must not read a foreign bit pattern as a classic request.
state->endPointIsWillingECN = true;
EV << "ECN-setup SYN packet received\n";
}
+ // TCP Fast Open (RFC 7413): a validated-cookie SYN carrying data is accepted and
+ // delivered to the app now, ahead of the 3WHS completing -- readHeaderOptions()
+ // above has already run processFastOpenOption() and populated
+ // state->fastopenCookie*. Advancing rcv_nxt here (before sendSynAck() below)
+ // makes the SYN-ACK's ack number naturally cover the SYN and the data in one
+ // segment, with no change needed to sendSynAck() itself.
+ bool tfoAttempted = state->fastopenCookieRequested || state->fastopenCookieValid || state->fastopenSendCookieOption;
+ // Cookie-less mode (RFC 7413 section 4.1.3, sysctl TFO_SERVER_COOKIE_NOT_REQD):
+ // the listener accepts the SYN data even when the SYN carries no cookie option
+ // at all, so fastopenCookieValid (and tfoAttempted) stay false here.
+ if (state->fastopenServerEnabled && (state->fastopenCookieValid || state->fastopenAcceptWithoutCookie)) {
+ // The TFO acceptance itself is payload-independent: a zero-payload SYN
+ // with a valid cookie still creates a fully-accelerated connection
+ // whose app may respond from SYN_RCVD (basic-zero-payload scripts).
+ state->fastopenAccelerated = true;
+ B payloadLen = B(tcpSegment->getByteLength()) - tcpHeader->getHeaderLength();
+ if (payloadLen > B(0) && hasEnoughSpaceForSegmentInReceiveQueue(tcpSegment, tcpHeader)) {
+ updateRcvQueueVars();
+ // insertBytesFromSegment() indexes the payload by tcpHeader's own
+ // SequenceNo, which is correct for a plain data segment but is the SYN's
+ // OWN sequence number here (RFC 793: SYN consumes one sequence number, so
+ // the data actually starts at SEG.SEQ+1) -- pass a sequence-shifted copy
+ // of the header so the receive queue doesn't mistake the first data byte
+ // for a 1-byte overlap with the already-initialized rcv_nxt (=IRS+1) and
+ // silently drop it.
+ auto synShiftedHeader = staticPtrCast(tcpHeader->dupShared());
+ synShiftedHeader->setSequenceNo(tcpHeader->getSequenceNo() + 1);
+ state->rcv_nxt = receiveQueue->insertBytesFromSegment(tcpSegment, synShiftedHeader);
+ updateRcvQueueVars();
+ sendAvailableDataToApp(); // deliver before the 3WHS completes -- the RFC 7413 win
+ state->fastopenSynDataAccepted = true; // surfaced as TCPI_OPT_SYN_DATA in tcp_info
+ EV_INFO << "Fast Open: " << (state->fastopenCookieValid ? "cookie valid" : "no cookie required")
+ << ", accepting " << payloadLen
+ << " bytes of SYN data before handshake completion\n";
+ }
+ }
+
sendSynAck();
startSynRexmitTimer();
@@ -847,7 +1189,14 @@ TcpEventCode TcpConnection::processSynInListen(Packet *tcpSegment, const PtrgetByteLength()) > tcpHeader->getHeaderLength()) {
+ // Also skipped when the Fast Open block above already consumed the payload
+ // (fastopenSynDataAccepted): in the cookie-less mode tfoAttempted is false
+ // (no cookie option was present), and re-inserting here with the UNSHIFTED
+ // sequence number -- after rcv_nxt has already advanced past the whole
+ // payload -- would compute a negative remainder and crash in peekDataAt()
+ // ("offset is out of range").
+ if (!tfoAttempted && !state->fastopenSynDataAccepted
+ && B(tcpSegment->getByteLength()) > tcpHeader->getHeaderLength()) {
updateRcvQueueVars();
if (hasEnoughSpaceForSegmentInReceiveQueue(tcpSegment, tcpHeader)) { // enough freeRcvBuffer in rcvQueue for new segment?
@@ -889,8 +1238,16 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
//"
if (tcpHeader->getAckBit()) {
if (seqLE(tcpHeader->getAckNo(), state->iss) || seqGreater(tcpHeader->getAckNo(), state->snd_nxt)) {
- if (tcpHeader->getRstBit())
+ if (tcpHeader->getRstBit()) {
EV_DETAIL << "ACK+RST bit set but wrong AckNo, ignored\n";
+ // TCP Fast Open active blackhole detection: an out-of-order RST
+ // (wrong AckNo) while a data-carrying TFO SYN is still outstanding
+ // is a classic middlebox-interference symptom -- some boxes that
+ // don't understand the TFO option strip it and desync sequence
+ // numbers, producing a stray RST like this one.
+ if (state->fastopenSynDataLen > 0)
+ tcpMain->recordFastOpenBlackhole();
+ }
else {
EV_DETAIL << "ACK bit set but wrong AckNo, sending RST\n";
sendRst(tcpHeader->getAckNo(), destAddr, srcAddr, tcpHeader->getDestPort(), tcpHeader->getSrcPort());
@@ -945,6 +1302,7 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
//
state->rcv_nxt = tcpHeader->getSequenceNo() + 1;
state->rcv_adv = state->rcv_nxt + state->rcv_wnd;
+ state->rcv_mwnd_seq = state->rcv_adv;
emit(rcvAdvSignal, state->rcv_adv);
@@ -955,10 +1313,9 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
state->snd_una = tcpHeader->getAckNo();
sendQueue->discardUpTo(state->snd_una);
- if (state->sack_enabled)
- rexmitQueue->discardUpTo(state->snd_una);
+ rexmitQueue->discardUpTo(state->snd_una);
- // although not mentioned in RFC 793, seems like we have to pick up
+ // although not mentioned in RFC 9293, seems like we have to pick up
// initial snd_wnd from the segment here.
updateWndInfo(tcpHeader, true);
}
@@ -981,20 +1338,38 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
if (seqGreater(state->snd_una, state->iss)) {
EV_INFO << "SYN+ACK bits set, connection established.\n";
+ // TCP Fast Open client (RFC 7413): the SYN carried data and the
+ // SYN-ACK acked ALL of it -- surface as TCPI_OPT_SYN_DATA, the
+ // same tcp_info bit the server side sets. Linux tp->syn_data_acked
+ // is true only when nothing of the SYN data remains unacked
+ // (tcp_rcv_fastopen_synack: syn_data && !data); a partial ack
+ // leaves it clear (syn-data-partial-or-over-ack: 9 of 18 bytes).
+ if (state->fastopenSynDataLen > 0
+ && seqGE(state->snd_una, state->iss + 1 + state->fastopenSynDataLen))
+ state->fastopenSynDataAccepted = true;
+
+
// RFC says "continue processing at the sixth step below where
// the URG bit is checked". Those steps deal with: URG, segment text
// (and PSH), and FIN.
// Now: URG and PSH we don't support yet; in SYN+FIN we ignore FIN;
// with segment text we just take it easy and put it in the receiveQueue
// -- we'll forward it to the user when more data arrives.
- if (tcpHeader->getFinBit())
- EV_DETAIL << "SYN+ACK+FIN received: ignoring FIN\n";
+ bool synAckFin = tcpHeader->getFinBit();
if (B(tcpSegment->getByteLength()) > tcpHeader->getHeaderLength()) {
updateRcvQueueVars();
if (hasEnoughSpaceForSegmentInReceiveQueue(tcpSegment, tcpHeader)) { // enough freeRcvBuffer in rcvQueue for new segment?
- receiveQueue->insertBytesFromSegment(tcpSegment, tcpHeader); // TODO forward to app, etc.
+ // advance rcv_nxt over the SYN-ACK's data (RFC 793 permits data on
+ // SYN-ACK; a TFO server may respond in the same segment), so that
+ // the handshake ACK acknowledges it. The SYN consumes one
+ // sequence number, so index the payload from SEG.SEQ+1 via a
+ // sequence-shifted header copy (same pattern as the TFO server's
+ // SYN-data acceptance in processSynInListen()).
+ auto synShiftedHeader = staticPtrCast(tcpHeader->dupShared());
+ synShiftedHeader->setSequenceNo(tcpHeader->getSequenceNo() + 1);
+ state->rcv_nxt = receiveQueue->insertBytesFromSegment(tcpSegment, synShiftedHeader);
}
else { // not enough freeRcvBuffer in rcvQueue for new segment
state->tcpRcvQueueDrops++; // update current number of tcp receive queue drops
@@ -1012,14 +1387,128 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
if (tcpHeader->getHeaderLength() > TCP_MIN_HEADER_LENGTH) // Header options present?
readHeaderOptions(tcpHeader);
+ // Fast Open option-form fallback (RFC 7413 appendix A). A cookie REQUEST
+ // that comes back with no Fast Open option at all usually means a
+ // middlebox or an older server that only understands the experimental
+ // kind-254 encoding, so remember to retry this destination that way --
+ // Linux does the same via tcp_metrics. Checked on the wire rather than
+ // through a state flag because readHeaderOptions() has no reason to
+ // record the absence of an option.
+ // Only when this SYN-ACK answers a SYN that actually carried the option:
+ // Linux drops the Fast Open option from SYN RETRANSMITS, so once we have
+ // retransmitted, a cookie-less SYN-ACK says nothing about the server's
+ // option dialect -- it answered a deliberately bare SYN, and we must keep
+ // requesting with kind 34 rather than falling back to kind 254.
+ if (state->fastopenCookieRequestPending && state->fastopenSynCarriedOption
+ && state->syn_rexmit_count == 0) {
+ bool sawFastOpenOption = false;
+ for (unsigned int i = 0; i < tcpHeader->getHeaderOptionArraySize(); i++) {
+ short kind = tcpHeader->getHeaderOption(i)->getKind();
+ if (kind == TCPOPTION_TCP_FASTOPEN || kind == TCPOPTION_RFC3692_STYLE_EXPERIMENT_2) {
+ sawFastOpenOption = true;
+ break;
+ }
+ }
+ if (!sawFastOpenOption) {
+ // Which form THIS request went out in -- nothing has touched the
+ // cache entry since (no cookie was learned), so it still answers
+ // that. The escalation is one-way and capped, so the experimental
+ // retry happens exactly once before the standard kind takes over
+ // again for good.
+ bool usedExpOption = tcpMain->getFastOpenUseExpOption(remoteAddr);
+ tcpMain->noteFastOpenCookieRequestUnanswered(remoteAddr, usedExpOption);
+ EV_INFO << "Fast Open: kind-" << (usedExpOption ? 254 : 34)
+ << " cookie request went unanswered; requesting from " << remoteAddr.str()
+ << " with kind " << (tcpMain->getFastOpenUseExpOption(remoteAddr) ? 254 : 34)
+ << " next time\n";
+ }
+ }
+
+ // Linux tcp_rcv_fastopen_synack(): a TFO connection's SYN-ACK
+ // refreshes the cached peer MSS EVERY time (the cookie only when
+ // one is present) -- a later cookie-less SYN-ACK advertising a
+ // larger MSS restores the cache after an earlier small-MSS server
+ // shrank it (syn-data-only-syn-acked's 1040 -> 1460 -> 1420-cap
+ // sequence pins this). Cache the RAW advertised value, not
+ // snd_mss: a local TCP_MAXSEG clamp on THIS connection must not
+ // shrink the metrics cache (the kernel reparses the SYN-ACK to
+ // bypass the user clamp; syn-data-mss pins the next SYN's
+ // 1300-byte payload from an advertised 1340 despite this
+ // connection's TCP_MAXSEG 1040).
+ if (state->fastopenRequested)
+ tcpMain->updateFastOpenCachedMss(remoteAddr,
+ state->peerAdvertisedMss > 0 ? state->peerAdvertisedMss : state->snd_mss);
+
+ // RFC 7323 / Linux tcp_rcv_synsent_state_process (PAWSACTIVEREJECTED):
+ // a SYN-ACK whose TSecr does not echo anything this connection could
+ // have sent (it must lie between the SYN's send time and now on our
+ // timestamp clock) is repelled with and the
+ // segment is dropped -- the connection stays in SYN_SENT awaiting a
+ // valid SYN-ACK (synack-data TEST5's deliberate bad-ecr probe).
+ if (state->rcv_initial_ts && state->lastRcvdTSecr != 0 && state->handshakeSentTime >= SIMTIME_ZERO) {
+ uint32_t tsLow = convertSimtimeToTS(state->handshakeSentTime);
+ uint32_t tsHigh = convertSimtimeToTS(simTime());
+ if (seqLess(state->lastRcvdTSecr, tsLow) || seqGreater(state->lastRcvdTSecr, tsHigh)) {
+ EV_WARN << "SYN-ACK TSecr " << state->lastRcvdTSecr << " outside [" << tsLow << ", "
+ << tsHigh << "] -- repelling with RST (PAWSACTIVEREJECTED)\n";
+ sendRst(tcpHeader->getAckNo());
+ return TCP_E_IGNORE;
+ }
+ }
+
// notify tcpAlgorithm (it has to send ACK of SYN) and app layer
state->ack_now = true;
- tcpAlgorithm->established(true);
- tcpMain->emit(Tcp::tcpConnectionAddedSignal, this);
- sendEstabIndicationToApp();
-
- // ECN
- if (state->ecnSynSent) {
+ state->snd_effmss = calculateEffectiveMss();
+
+ // ECN. Resolved BEFORE tcpAlgorithm->established(true) below: that call
+ // synchronously sends the connection-completing 3rd ACK, and AccECN needs that
+ // ACK's ACE field to reflect the just-negotiated state (matching the kernel's
+ // handling of the analogous 3rd-ACK case) -- ordering matters here in a way it
+ // never did for classic ECN, which doesn't touch this particular ACK's flags.
+ if (state->aeSynSent) {
+ // draft-ietf-tcpm-accurate-ecn 3WHS: decode the SYN-ACK's (ECE,CWR,AE) triple
+ // in response to our SEWA (AccECN-requesting) SYN.
+ // Table 2 of RFC 9768 / Linux tcp_ecn_rcv_synack: the ACE value
+ // (AE<<2 | CWR<<1 | ECE) of the SYN-ACK decides the mode.
+ // 0b000 and 0b111 = no ECN; 0b001 = peer speaks only classic
+ // ECN, fall back; EVERY other value (0b010..0b110) = AccECN
+ // accepted, the value additionally encoding how our SYN
+ // arrived (serverside_accecn_disabled1 pins 0b101 as accept).
+ uint8_t synAckAce = (uint8_t)((tcpHeader->getAeBit() ? 4 : 0)
+ | (tcpHeader->getCwrBit() ? 2 : 0) | (tcpHeader->getEceBit() ? 1 : 0));
+ if (synAckAce == 0 || synAckAce == 7) {
+ state->ect = false;
+ EV << "AccECN request received a non-ECN-setup SYN-ACK... ECN is disabled.\n";
+ }
+ else if (synAckAce == 1) {
+ state->ecnMode = TCP_ECN_MODE_RFC3168;
+ state->ect = true;
+ EV << "AccECN request received classic-ECN SYN-ACK... falling back to RFC 3168 ECN.\n";
+ }
+ else {
+ state->accEcnNegotiated = true;
+ state->ect = true;
+ // The handshake-completing ACK -- sent synchronously from the
+ // established() call a few lines below -- reflects this SYN-ACK's
+ // IP-ECN codepoint back to the server (RFC 9768 section 3.2.3.2),
+ // telling it whether the network preserved its ECN field. Capture
+ // it while the EcnInd tag is still attached and arm that one ACK;
+ // sendToIP() consumes the flag and returns to the CE counter after.
+ state->accEcnReflectCodepoint = receivedEcnCodepoint(tcpSegment);
+ state->accEcnReflectAce = true;
+ // A CE-marked SYN-ACK is itself a CE-marked packet received, so it
+ // seeds the CE counter at 1 (RFC 9768: the client's r.cep starts at
+ // 6 rather than 5 in that case) and every later ACK carries ACE=6.
+ // Tcp.cc's ingest-time counter cannot do this: accEcnNegotiated only
+ // becomes true here, while processing that very segment.
+ if (state->accEcnReflectCodepoint == IP_ECN_CE)
+ state->rcvCePkts++;
+ EV << "AccECN-setup SYN-ACK received (ACE=" << (int)synAckAce
+ << ")... AccECN is enabled. (IP-ECN " << state->accEcnReflectCodepoint << ")\n";
+ }
+ state->aeSynSent = false;
+ }
+ else if (state->ecnSynSent) {
if (tcpHeader->getEceBit() && !tcpHeader->getCwrBit()) {
state->ect = true;
EV << "ECN-setup SYN-ACK packet was received... ECN is enabled.\n";
@@ -1036,6 +1525,77 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
EV << "ECN-setup SYN-ACK packet was received... ECN is disabled.\n";
}
+ // Seed the RTT estimator from the handshake RTT (Linux measures the
+ // SYN<->SYN-ACK exchange via tcp_ack_update_rtt/tcp_synack_rtt_meas and
+ // enters ESTABLISHED with srtt/rttvar -- and hence the first RTO and any
+ // TLP probe timeout -- already RTT-scaled instead of the 1 s initial
+ // default). Karn: skipped if our handshake segment was retransmitted.
+ if (state->syn_rexmit_count == 0 && state->handshakeSentTime >= SIMTIME_ZERO)
+ tcpAlgorithm->rttMeasurementComplete(state->handshakeSentTime, simTime());
+
+ // RFC 7413 section 4.1: SYN data the SYN-ACK did NOT acknowledge
+ // (server acked only the SYN, or a partial range) is retransmitted
+ // immediately on connection establishment -- Linux does this from
+ // tcp_rcv_synsent_state_process, emitting the retransmit as the FIRST
+ // post-handshake segment with the handshake ACK piggybacked on it, not
+ // a separate bare ACK. Pull snd_nxt back to the unacked point so
+ // established()'s send-data-with-first-ACK path emits exactly that.
+ bool fastopenSynDataRexmit = state->fastopenSynDataLen > 0 && seqLess(state->snd_una, state->snd_max);
+ if (fastopenSynDataRexmit) {
+ EV_INFO << "Fast Open: SYN data [" << state->snd_una << ", " << state->snd_max
+ << ") not acknowledged by the SYN-ACK, retransmitting with the handshake ACK\n";
+ state->snd_nxt = state->snd_una;
+ // afterRto is the sanctioned "snd_nxt deliberately pulled back"
+ // signal: without it sendData() immediately resets snd_nxt to
+ // snd_max and nothing is retransmitted. It auto-clears once
+ // snd_nxt catches back up to snd_max.
+ state->afterRto = true;
+ // The unacked SYN data no longer counts as in flight (Linux's
+ // fallback requeues the skb as plain pending data, packets_out
+ // drops to 0): without the lost mark the pipe still carries it
+ // and, with the post-SYN-rexmit IW of one segment, allowedToSend
+ // collapses to zero and only a bare handshake ACK leaves
+ // (cookie-less-sendto's non-blocking test).
+ if (state->sack_enabled && rexmitQueue->getQueueLength() > 0)
+ rexmitQueue->markLost(state->snd_una, state->snd_max);
+ // No Nagle/PSH special-casing needed anymore: Minshall's check
+ // never holds this partial (no unacked SMALL segment is in
+ // flight), and the PSH comes from the push boundary sendSyn()
+ // recorded at the SYN-data end (Linux marks the syn_data skb
+ // with TCPHDR_PSH at creation).
+ }
+
+ // RFC 793 permits FIN on the SYN-ACK; Linux processes it and lands
+ // in CLOSE_WAIT (tcp_fin) -- fastopen/client/synack-data's TEST2
+ // expects the single handshake ACK to cover data AND FIN (ack 1402).
+ // Advance rcv_nxt over the FIN BEFORE established(true) sends that
+ // ACK; the state hop goes SYN_SENT -> ESTABLISHED here, then the
+ // returned RCV_FIN takes ESTABLISHED -> CLOSE_WAIT in the caller
+ // (stateEntered defers TCP_I_PEER_CLOSED until the data is read,
+ // like a normal FIN behind undelivered data).
+ if (synAckFin) {
+ EV_INFO << "FIN on the SYN-ACK: advancing rcv_nxt over the FIN, will enter CLOSE_WAIT\n";
+ state->fin_rcvd = true;
+ state->rcv_fin_seq = state->rcv_nxt;
+ state->rcv_nxt = state->rcv_fin_seq + 1;
+ }
+
+ // notify tcpAlgorithm (it has to send ACK of SYN) and app layer
+ state->ack_now = true;
+ tcpAlgorithm->established(true);
+ tcpMain->emit(Tcp::tcpConnectionAddedSignal, this);
+ sendEstabIndicationToApp();
+ // deliver any data that rode the SYN-ACK (inserted above) -- the
+ // normal per-segment delivery only runs in the data-transfer
+ // states, so without this the app would never see these bytes
+ // (and a poll() right after the handshake would miss POLLIN)
+ sendAvailableDataToApp();
+
+ if (synAckFin) {
+ performStateTransition(TCP_E_RCV_SYN_ACK); // SYN_SENT -> ESTABLISHED now...
+ return TCP_E_RCV_FIN; // ...so the caller's transition lands in CLOSE_WAIT
+ }
+
// This will trigger transition to ESTABLISHED. Timers and notifying
// app will be taken care of in stateEntered().
return TCP_E_RCV_SYN_ACK;
@@ -1051,9 +1611,24 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
// has been reached, return.
//"
EV_INFO << "SYN bit set: sending SYN+ACK\n";
+ // Simultaneous open: consume the crossing SYN's options BEFORE building
+ // our SYN-ACK (mirrors processSegmentInListen()) -- otherwise
+ // rcv_sack_perm/ws/ts stay unset and the reply advertises nothing.
+ if (tcpHeader->getHeaderLength() > TCP_MIN_HEADER_LENGTH)
+ readHeaderOptions(tcpHeader);
state->snd_max = state->snd_nxt = state->iss;
+ emit(sndMaxSignal, state->snd_max);
sendSynAck();
startSynRexmitTimer();
+ // TFO simultaneous open: our SYN's data stays queued and OUTSTANDING.
+ // Linux keeps snd_nxt spanning it (the SYN-ACK reuses the SYN skb's
+ // sequence, nothing is rewound), so the peer's eventual SYN-ACK acking
+ // the data (ack 1001) is acceptable and the dup-segment ACK goes out
+ // with SEQ = 1001, not a SYN-ACK retransmit (simultaneous-fast-open).
+ if (state->fastopenSynDataLen > 0) {
+ state->snd_max = state->snd_nxt = state->iss + 1 + state->fastopenSynDataLen;
+ emit(sndMaxSignal, state->snd_max);
+ }
// Note: code below is similar to processing SYN in LISTEN.
@@ -1061,24 +1636,13 @@ TcpEventCode TcpConnection::processSegmentInSynSent(Packet *tcpSegment, const Pt
if (tcpHeader->getFinBit())
EV_DETAIL << "SYN+FIN received: ignoring FIN\n";
- // We don't send text in SYN or SYN+ACK, but accept it. Otherwise
- // there isn't much left to do: RST, SYN, ACK, FIN got processed already,
- // so there's only URG and PSH left to handle.
- if (B(tcpSegment->getByteLength()) > tcpHeader->getHeaderLength()) {
- updateRcvQueueVars();
-
- if (hasEnoughSpaceForSegmentInReceiveQueue(tcpSegment, tcpHeader)) { // enough freeRcvBuffer in rcvQueue for new segment?
- receiveQueue->insertBytesFromSegment(tcpSegment, tcpHeader); // TODO forward to app, etc.
- }
- else { // not enough freeRcvBuffer in rcvQueue for new segment
- state->tcpRcvQueueDrops++; // update current number of tcp receive queue drops
-
- emit(tcpRcvQueueDropsSignal, state->tcpRcvQueueDrops);
-
- EV_WARN << "RcvQueueBuffer has run out, dropping segment\n";
- return TCP_E_IGNORE;
- }
- }
+ // Data on the crossing SYN is DISCARDED: Linux tcp_rcv_synsent_state_
+ // process moves to SYN_RECV and drops the segment -- a client socket
+ // has no TFO server context to accept SYN data into, and the golden
+ // pins the peer retransmitting it after the handshake
+ // (simultaneous-fast-open: "The other end retries").
+ if (B(tcpSegment->getByteLength()) > tcpHeader->getHeaderLength())
+ EV_DETAIL << "Discarding data on the crossing SYN (simultaneous open)\n";
if (tcpHeader->getUrgBit() || tcpHeader->getPshBit())
EV_DETAIL << "Ignoring URG and PSH bits in SYN\n"; // TODO
@@ -1111,16 +1675,17 @@ TcpEventCode TcpConnection::processRstInSynReceived(const Ptr&
sendQueue->discardUpTo(sendQueue->getBufferEndSeq()); // flush send queue
- if (state->sack_enabled)
- rexmitQueue->discardUpTo(rexmitQueue->getBufferEndSeq()); // flush rexmit queue
+ rexmitQueue->discardUpTo(rexmitQueue->getBufferEndSeq()); // flush rexmit queue
if (state->active) {
// signal "connection refused"
sendIndicationToApp(TCP_I_CONNECTION_REFUSED);
}
- // on RCV_RST, FSM will go either to LISTEN or to CLOSED, depending on state->active
- // FIXME if this was a forked connection, it should rather close than go back to listening (otherwise we'd now have two listening connections with the original one!)
+ // on RCV_RST the FSM goes to CLOSED for an active open, a FORKED connection
+ // (a Linux child socket must die -- re-listening would duplicate the still-
+ // existing listener) or a TFO-accelerated connection (app-visible state
+ // exists); only a plain non-forked passive open returns to LISTEN (RFC 793).
return TCP_E_RCV_RST;
}
@@ -1130,9 +1695,13 @@ bool TcpConnection::processAckInEstabEtc(Packet *tcpSegment, const PtrgetByteLength() - tcpHeader->getHeaderLength().get();
- // ECN
+ // ECN. AccECN connections repurpose eceBit as part of the post-handshake ACE counter
+ // (decoded separately below, near the end of this function) -- classic ECE-echo
+ // consumption must not also read it here, or congestion control (TcpReno/TcpCubic/DcTcp,
+ // all gated on gotEce) would spuriously react to ACE bit-pattern noise instead of a real
+ // congestion signal.
TcpStateVariables *state = getStateForUpdate();
- if (state && state->ect) {
+ if (state && state->ect && !state->accEcnNegotiated) {
if (tcpHeader->getEceBit() == true)
EV_INFO << "Received packet with ECE\n";
@@ -1165,42 +1734,34 @@ bool TcpConnection::processAckInEstabEtc(Packet *tcpSegment, const Ptrsnd_una, tcpHeader->getAckNo())) {
- //
- // duplicate ACK? A received TCP segment is a duplicate ACK if all of
- // the following apply:
- // (1) snd_una == ackNo
- // (2) segment contains no data
- // (3) there's unacked data (snd_una != snd_max)
- //
- // Note: ssfnet uses additional constraint "window is the same as last
- // received (not an update)" -- we don't do that because window updates
- // are ignored anyway if neither seqNo nor ackNo has changed.
- //
- if (state->snd_una == tcpHeader->getAckNo() && payloadLength == 0 && state->snd_una != state->snd_max) {
- state->dupacks++;
-
- emit(dupAcksSignal, state->dupacks);
-
+ // RFC 5961 Section 5.2 (and RFC 793 3.9): SEG.ACK must lie within
+ // [SND.UNA - MAX.SND.WND, SND.NXT]; an ACK older than the largest
+ // window the peer ever advertised cannot be a delayed duplicate --
+ // discard it and send a challenge ACK (SEQ=SND.NXT, ACK=RCV.NXT),
+ // exactly what sendAck() builds (rfc5961 ack-out-of-window pins both
+ // edges; the upper edge was already handled as ack-for-unsent-data).
+ if (state->max_window > 0
+ && seqLess(tcpHeader->getAckNo(), state->snd_una - state->max_window))
+ {
+ EV_INFO << "ACK below SND.UNA - MAX.SND.WND (RFC 5961 5.2): discarding and sending challenge ACK\n";
+ sendAck();
+ return false; // means "drop"
+ }
+ if (state->snd_una == tcpHeader->getAckNo() && payloadLength == 0) {
// we need to update send window even if the ACK is a dupACK, because rcv win
- // could have been changed if faulty data receiver is not respecting the "do not shrink window" rule
+ // could have been changed if faulty data receiver is not respecting the "do not shrink window" rule.
+ // Also when NOTHING is in flight (snd_una == snd_max): a pure
+ // window-update ACK that reopens a zero window is the very signal
+ // that ends the persist state -- ignoring it left queued data
+ // waiting for the next zero-window probe (Linux FLAG_WIN_UPDATE ->
+ // tcp_data_snd_check transmits immediately; slow-start-after-
+ // win-update pins data on the wire right at the reopening ACK).
updateWndInfo(tcpHeader);
- tcpAlgorithm->receivedDuplicateAck();
- }
- else {
- // if doesn't qualify as duplicate ACK, just ignore it.
- if (payloadLength == 0) {
- if (state->snd_una != tcpHeader->getAckNo())
- EV_DETAIL << "Old ACK: ackNo < snd_una\n";
- else if (state->snd_una == state->snd_max)
- EV_DETAIL << "ACK looks duplicate but we have currently no unacked data (snd_una == snd_max)\n";
- }
-
- // reset counter
- state->dupacks = 0;
-
- emit(dupAcksSignal, state->dupacks);
+ if (state->snd_una != state->snd_max && !state->sack_enabled)
+ rexmitQueue->addInferredSack();
}
+ tcpAlgorithm->receivedAckForAlreadyAckedData(tcpHeader.get(), payloadLength);
}
else if (seqLE(tcpHeader->getAckNo(), state->snd_max)) {
// ack in window.
@@ -1209,20 +1770,32 @@ bool TcpConnection::processAckInEstabEtc(Packet *tcpSegment, const Ptrsnd_max - state->snd_una);
+ // delivered-bytes accounting (RFC 8985/6937): count the newly cumulatively
+ // acknowledged bytes (the newly-SACKed part is counted in processSACKOption).
+ if (seqGreater(state->snd_una, old_snd_una)) {
+ state->deliveredBytes += state->snd_una - old_snd_una;
+ emit(deliveredSignal, (unsigned long)state->deliveredBytes);
+ }
+
// after retransmitting a lost segment, we may get an ack well ahead of snd_nxt
if (seqLess(state->snd_nxt, state->snd_una))
state->snd_nxt = state->snd_una;
- // RFC 1323, page 36:
- // "If SND.UNA < SEG.ACK =< SND.NXT then, set SND.UNA <- SEG.ACK.
+ // RFC 4821: an outstanding MTU probe that is now cumulatively acknowledged
+ // proves the path carries a segment that size.
+ if (state->mtupProbeSize != 0 && seqGE(state->snd_una, state->mtupProbeSeqEnd))
+ mtupProbeSucceeded();
+
+ // RFC 7323, page 43
+ // "If SND.UNA < SEG.ACK <= SND.NXT then, set SND.UNA <- SEG.ACK.
// Also compute a new estimate of round-trip time. If Snd.TS.OK
- // bit is on, use my.TSclock - SEG.TSecr; otherwise use the
+ // bit is on, use Snd.TSclock - SEG.TSecr; otherwise use the
// elapsed time since the first segment in the retransmission
// queue was sent. Any segments on the retransmission queue
// which are thereby entirely acknowledged."
if (state->ts_enabled)
tcpAlgorithm->rttMeasurementCompleteUsingTS(getTSecr(tcpHeader));
- // Note: If TS is disabled the RTT measurement is completed in TcpBaseAlg::receivedDataAck()
+ // Note: If TS is disabled the RTT measurement is completed in TcpAlgorithmBase::receivedAckForUnackedData()
uint32_t discardUpToSeq = state->snd_una;
@@ -1234,42 +1807,225 @@ bool TcpConnection::processAckInEstabEtc(Packet *tcpSegment, const PtrsegmentsAcked(old_snd_una, discardUpToSeq);
+
// acked data no longer needed in send queue
sendQueue->discardUpTo(discardUpToSeq);
+ // TCP_INFO trio (busy_time): read-only bookkeeping -- if this ACK just
+ // caught snd_una up to snd_max with nothing left queued either, the
+ // connection has gone fully idle. See enqueueSendCommandData() for the
+ // matching "became busy" entry.
+ if (state->busyStartTime >= SIMTIME_ZERO && state->snd_una == state->snd_max
+ && sendQueue->getBytesAvailable(state->snd_nxt) == 0)
+ {
+ state->busyTimeAccumulated += simTime() - state->busyStartTime;
+ state->busyStartTime = -1;
+ }
+
// acked data no longer needed in rexmit queue
+ rexmitQueue->discardUpTo(discardUpToSeq);
+
+ // A plain cumulative ACK carries no SACK option, so processSACKOption()
+ // does not run to recompute the SACK scoreboard byte count. Refresh it
+ // after the discard so tcpi_sacked reflects only what is still SACKed
+ // above snd_una (Linux tp->sacked_out drops as snd_una catches up); a full
+ // ACK that ends recovery must report 0, not the stale pre-ACK count. The
+ // next SACK's delivered-delta baseline (sackedBytes_old) is re-taken from
+ // this value in processSACKOption(), so the PRR accounting stays consistent.
if (state->sack_enabled)
- rexmitQueue->discardUpTo(discardUpToSeq);
+ state->sackedBytes = rexmitQueue->getTotalAmountOfSackedBytes();
updateWndInfo(tcpHeader);
// if segment contains data, wait until data has been forwarded to app before sending ACK,
// otherwise we would use an old ACKNo
- if (payloadLength == 0 && fsm.getState() != TCP_S_SYN_RCVD) {
- // notify
- tcpAlgorithm->receivedDataAck(old_snd_una);
-
- // in the receivedDataAck we need the old value
- state->dupacks = 0;
-
- emit(dupAcksSignal, state->dupacks);
- }
+ //
+ // The handshake-completing ACK (fsm still SYN_RCVD here) is normally
+ // excluded: it only acks the SYN, and the algorithm was just
+ // initialized by established() above. But a TCP Fast Open server may
+ // have sent response DATA from SYN_RCVD -- when the handshake ACK
+ // also acks beyond the SYN-ACK's sequence slot (iss+1), it is a data
+ // ack and must run the algorithm's ack processing, or the data's
+ // REXMIT/probe timers stay armed after everything is acked.
+ bool acksFastOpenData = state->fastopenSynDataAccepted && seqGreater(tcpHeader->getAckNo(), state->iss + 1);
+ if (payloadLength == 0 && (fsm.getState() != TCP_S_SYN_RCVD || acksFastOpenData))
+ tcpAlgorithm->receivedAckForUnackedData(old_snd_una);
}
else {
ASSERT(seqGreater(tcpHeader->getAckNo(), state->snd_max)); // from if-ladder
// send an ACK, drop the segment, and return.
- tcpAlgorithm->receivedAckForDataNotYetSent(tcpHeader->getAckNo());
- state->dupacks = 0;
-
- emit(dupAcksSignal, state->dupacks);
+ tcpAlgorithm->receivedAckForUnsentData(tcpHeader->getAckNo());
return false; // means "drop"
}
+ // AccECN: ACE field read side -- mod-8 delta resolution
+ // (design reference: tcp_accecn_process/__tcp_accecn_process, tcp_input.c, cited for the
+ // naive-delta + safeDelta shape only, reimplemented against INET's own byte-oriented
+ // state). Skipped on the handshake-completing ACK (fsm still SYN_RCVD here, i.e. the
+ // very first ACE value this side has ever seen from the peer) -- there's no prior
+ // baseline to diff against yet.
+ //
+ // Forward-progress guard: __tcp_accecn_process returns 0 up front unless the
+ // ACK makes forward progress (FLAG_FORWARD_PROGRESS | FLAG_TS_PROGRESS). An
+ // ACK that acks no new data (snd_una did not advance, so deliveredBytes is
+ // unchanged since this segment's prrDeliveredMark snapshot) is a pure /
+ // duplicate ACK and must not move the CE counters -- neither the ACE-field
+ // delta nor the AccECN option's CEB delta. readHeaderOptions() left the
+ // option baseline (peerReportedCeBytes) unadvanced, so any accumulated delta
+ // is instead consumed by the next forward-progress ACK, exactly as Linux
+ // defers it. TS-only progress (FLAG_TS_PROGRESS, a positive ts_recent delta
+ // recorded per-segment as accEcnTsProgress) also qualifies: an ACK that acks
+ // no new data but carries a FRESH timestamp is not a reordered duplicate, so
+ // its ACE value is trustworthy (accecn tsprogress/tsnoprogress pin both sides
+ // of this: fresh TSval counts the fake CE, a stale TSval must not).
+ // AccECN third-ack ACE handling (RFC 9768 Table 4 / Linux tcp_accecn_third_ack,
+ // called from tcp_ecn_openreq_child): on the handshake-completing ACK the ACE
+ // field echoes how the SYN-ACK arrived (Table 3 handshake encoding, not yet a
+ // counter). 0b110 = "SYN-ACK was delivered CE-marked" seeds delivered_ce to 1
+ // -- which also aligns the mod-8 baseline, since the peer's own ACE counter
+ // started counting from that CE. Only a data-less ACK is validated, like Linux.
+ // (Linux additionally validates the claimed ECN field against what the SYN-ACK
+ // was sent with unless net.ipv4.tcp_ecn_fallback=0; the pinning script,
+ // accecn synack_ce_updates_delivered_ce, runs with fallback disabled.)
+ if (state->accEcnNegotiated && fsm.getState() == TCP_S_SYN_RCVD && payloadLength == 0) {
+ uint8_t handshakeAce = (uint8_t)((tcpHeader->getAeBit() ? 4 : 0)
+ | (tcpHeader->getCwrBit() ? 2 : 0) | (tcpHeader->getEceBit() ? 1 : 0));
+ if (handshakeAce == 6 && state->deliveredCePkts == 0) {
+ state->deliveredCePkts = 1;
+ emit(deliveredCeSignal, (unsigned long)state->deliveredCePkts);
+ EV_INFO << "AccECN third ACK: ACE=0b110, SYN-ACK was CE-marked -- delivered_ce seeded to 1\n";
+ }
+ else if (handshakeAce == 0 && state->ect) {
+ // Table 4 case 0x0: an ALL-ZERO ACE on the third ACK is invalid --
+ // a middlebox bleached the handshake feedback (Linux sets
+ // TCP_ACCECN_ACE_FAIL_RECV). Stop marking ECT; the ACE/option
+ // feedback machinery keeps running (negotiation_bleach pins
+ // [noecn] data segments that still carry ACE flags + the option).
+ EV_INFO << "AccECN third ACK: ACE=0b000 (bleached) -- disabling ECT marking\n";
+ state->ect = false;
+ }
+ }
+
+ if (state->accEcnNegotiated && fsm.getState() != TCP_S_SYN_RCVD
+ && (state->deliveredBytes != state->prrDeliveredMark || state->accEcnTsProgress)) {
+ bool ae = tcpHeader->getAeBit();
+ bool cwr = tcpHeader->getCwrBit();
+ bool ece = tcpHeader->getEceBit();
+ uint8_t receivedAce = (uint8_t)((ae ? 4 : 0) | (cwr ? 2 : 0) | (ece ? 1 : 0));
+
+ // deliveredPktsThisAck: INET has no segment-boundary tracking once bytes enter the
+ // (byte-range-based) rexmit-queue/send-queue model, so this approximates "packets"
+ // the same way Linux's own tcp_skb_pcount (GSO/TSO segment counting, which INET
+ // doesn't model either) ultimately reduces to for a non-offloaded sender: one MSS
+ // of newly-delivered bytes per packet. Reuses the existing prrDeliveredMark
+ // snapshot (process_RCV_SEGMENT, RFC 6937 PRR) rather than adding a second one.
+ uint64_t deliveredBytesThisAck = state->deliveredBytes - state->prrDeliveredMark;
+ uint32_t mss = state->snd_mss > 0 ? state->snd_mss : 1;
+ uint32_t deliveredPktsThisAck = (uint32_t)((deliveredBytesThisAck + mss - 1) / mss);
+
+ int delta = ((int)receivedAce - 5 - (int)(state->deliveredCePkts & 0x7)) & 0x7;
+ int safeDelta = delta;
+ if (deliveredPktsThisAck > 7) {
+ // Naive delta can't distinguish "the counter wrapped around more than once"
+ // from "it wrapped around once" when more than 8 packets were delivered in a
+ // single ACK -- resolve against the actual delivered-packet count instead.
+ safeDelta = (int)deliveredPktsThisAck - (((int)deliveredPktsThisAck - delta) & 0x7);
+ }
+
+ // Packets-acked EWMA (design reference: __tcp_accecn_process's pkts_acked_ewma,
+ // tcp_input.c; PKTS_ACKED_WEIGHT=PKTS_ACKED_PREC=6 reimplemented here). Tracks
+ // whether large ACKs are the NORM for this flow (receiver-side ACK
+ // compression / GRO). When they are, a big single-ACK delivered-packet count
+ // is expected and does NOT imply the mod-8 ACE counter wrapped, so the naive
+ // delta -- not safeDelta -- is the correct CE count. Updated on every ACK.
+ if (deliveredPktsThisAck > 0) {
+ if (state->pktsAckedEwma == 0)
+ state->pktsAckedEwma = deliveredPktsThisAck << 6; // PKTS_ACKED_PREC
+ else {
+ uint32_t e = state->pktsAckedEwma;
+ e = (((e << 6) - e) + (deliveredPktsThisAck << 6)) >> 6; // weight 6
+ state->pktsAckedEwma = std::min(e, 0xFFFF);
+ }
+ }
+
+ // AccECN TCP option: if this ACK also carried a valid AccECN
+ // option (readHeaderOptions() already ran and set accEcnOptionCebDeltaValid,
+ // before this function, for this same segment), its byte-exact CEB evidence can
+ // corroborate naiveDelta vs. safeDelta -- resolveAceDelta() picks whichever
+ // candidate's byte estimate is closer to the observed CE byte delta. Without the
+ // option, the packet-count-only safeDelta is used as-is.
+ int resolvedDelta = safeDelta;
+ long cebDeltaForTrace = -1;
+ if (state->accEcnOptionCebDeltaValid) {
+ // Compute the delta AND advance the peerReportedCeBytes baseline together,
+ // right here at the one place that actually consumes it -- readHeaderOptions()
+ // deliberately left the baseline untouched (see its own comment and the state
+ // field's) so this function's early-return path (an ACK beyond snd_max, above)
+ // can never advance the baseline while discarding the delta it implies.
+ uint32_t cebDelta = (state->accEcnOptionRawCeBytes - state->peerReportedCeBytes) & 0xFFFFFF;
+ resolvedDelta = resolveAceDelta(delta, safeDelta, cebDelta);
+ state->deliveredCeBytes += cebDelta;
+ state->peerReportedCeBytes = state->accEcnOptionRawCeBytes;
+ emit(deliveredCeBytesSignal, (unsigned long)state->deliveredCeBytes);
+ cebDeltaForTrace = (long)cebDelta;
+ }
+ else if (deliveredPktsThisAck > 7 && state->pktsAckedEwma > (4u << 6)) {
+ // No AccECN option to disambiguate, but this flow's ACKs routinely
+ // cover many packets (EWMA above ACK_COMP_THRESH=4): the large
+ // delivered-packet count is ACK compression, not an ACE counter wrap,
+ // so the naive mod-8 delta is the correct CE count rather than safeDelta.
+ resolvedDelta = delta;
+ }
+
+ state->deliveredCePkts += resolvedDelta;
+ emit(deliveredCeSignal, (unsigned long)state->deliveredCePkts);
+ EV_INFO << "AccECN ACE decode: receivedAce=" << (int)receivedAce
+ << " deliveredPktsThisAck=" << deliveredPktsThisAck
+ << " naiveDelta=" << delta << " safeDelta=" << safeDelta
+ << " cebDeltaValid=" << state->accEcnOptionCebDeltaValid
+ << " cebDelta=" << cebDeltaForTrace
+ << " resolvedDelta=" << resolvedDelta
+ << " deliveredCePkts=" << state->deliveredCePkts << "\n";
+ }
+
+ // ECT0/ECT1 delivered-byte accounting (tcpi_delivered_e0/e1_bytes). Unlike the
+ // ACE-field CE-packet delta above, these cumulative byte counters advance on ANY
+ // in-window ACK that carried a valid AccECN option, not only forward-progress
+ // ACKs -- a pure/duplicate ACK simply repeats the same counter, so its delta is 0.
+ // Gating this on forward progress (as the ACE block is) would drop the delta of a
+ // final ACK that only advances the cumulative ACK past already-in-flight data.
+ if (state->accEcnOptionE0DeltaValid) {
+ state->deliveredE0Bytes += (state->accEcnOptionRawE0Bytes - state->peerReportedEct0Bytes) & 0xFFFFFF;
+ state->peerReportedEct0Bytes = state->accEcnOptionRawE0Bytes;
+ }
+ if (state->accEcnOptionE1DeltaValid) {
+ state->deliveredE1Bytes += (state->accEcnOptionRawE1Bytes - state->peerReportedEct1Bytes) & 0xFFFFFF;
+ state->peerReportedEct1Bytes = state->accEcnOptionRawE1Bytes;
+ }
+
return true;
}
+int TcpConnection::resolveAceDelta(int naiveDelta, int safeDelta, uint32_t cebByteDelta) const
+{
+ if (naiveDelta == safeDelta)
+ return naiveDelta; // no ambiguity to resolve
+
+ uint32_t mss = state->snd_mss > 0 ? state->snd_mss : 1;
+ uint64_t naiveBytesEstimate = (uint64_t)naiveDelta * mss;
+ uint64_t safeBytesEstimate = (uint64_t)safeDelta * mss;
+ uint64_t naiveDiff = (cebByteDelta > naiveBytesEstimate) ? (cebByteDelta - naiveBytesEstimate) : (naiveBytesEstimate - cebByteDelta);
+ uint64_t safeDiff = (cebByteDelta > safeBytesEstimate) ? (cebByteDelta - safeBytesEstimate) : (safeBytesEstimate - cebByteDelta);
+ return (naiveDiff <= safeDiff) ? naiveDelta : safeDelta;
+}
+
// ----
void TcpConnection::process_TIMEOUT_CONN_ESTAB()
@@ -1332,19 +2088,34 @@ void TcpConnection::process_TIMEOUT_FIN_WAIT_2()
void TcpConnection::startSynRexmitTimer()
{
state->syn_rexmit_count = 0;
- state->syn_rexmit_timeout = TCP_TIMEOUT_SYN_REXMIT;
+ // Linux retransmits the SYN/SYN-ACK on the same initial RTO as data (1s;
+ // TCP_TIMEOUT_INIT), doubling per attempt. The initialRto parameter sets it.
+ state->syn_rexmit_timeout = tcpMain->par("initialRto");
rescheduleAfter(state->syn_rexmit_timeout, synRexmitTimer);
}
void TcpConnection::process_TIMEOUT_SYN_REXMIT(TcpEventCode& event)
{
- if (++state->syn_rexmit_count > MAX_SYN_REXMIT_COUNT) {
- EV_INFO << "Retransmission count during connection setup exceeds " << MAX_SYN_REXMIT_COUNT << ", giving up\n";
+ // Linux net.ipv4.tcp_syn_retries / TCP_SYNCNT: cap on SYN retransmissions
+ // (read live so a runtime-injected sockopt takes effect); -1 keeps INET's
+ // historical MAX_SYN_REXMIT_COUNT.
+ int synRetries = tcpMain->par("synRetries");
+ int maxSynRexmitCount = synRetries >= 0 ? synRetries : MAX_SYN_REXMIT_COUNT;
+ if (++state->syn_rexmit_count > maxSynRexmitCount) {
+ EV_INFO << "Retransmission count during connection setup exceeds " << maxSynRexmitCount << ", giving up\n";
// Note ABORT will take the connection to closed, and cancel CONN-ESTAB timer as well
event = TCP_E_ABORT;
return;
}
+ // TCP Fast Open active blackhole detection: repeated SYN-REXMITs on a
+ // connection whose SYN carried data suggest a middlebox is dropping/mangling
+ // TFO SYN+data specifically (a plain-SYN retransmit would usually get through
+ // sooner) -- matches the kernel's tcp_fastopen_active_should_disable() 3rd
+ // (index-2) consecutive-timeout trigger.
+ if (state->fastopenSynDataLen > 0 && state->syn_rexmit_count == TFO_BLACKHOLE_RTO_THRESHOLD)
+ tcpMain->recordFastOpenBlackhole();
+
EV_INFO << "Performing retransmission #" << state->syn_rexmit_count << "\n";
// resend what's needed
@@ -1362,11 +2133,23 @@ void TcpConnection::process_TIMEOUT_SYN_REXMIT(TcpEventCode& event)
stateName(fsm.getState()));
}
- // reschedule timer
- state->syn_rexmit_timeout *= 2;
-
- if (state->syn_rexmit_timeout > TCP_TIMEOUT_SYN_REXMIT_MAX)
- state->syn_rexmit_timeout = TCP_TIMEOUT_SYN_REXMIT_MAX;
+ // reschedule timer: Linux (tcp_syn_linear_timeouts, default 4) fires the first
+ // few CLIENT SYN retransmits at the initial RTO (linear spacing) before
+ // exponential backoff begins, so a briefly-lost handshake recovers quickly.
+ // The server's SYN-ACK retransmit (SYN_RCVD) doubles from the first attempt.
+ int synLinearTimeouts = tcpMain->par("synLinearTimeouts");
+ bool linearTimeout = (fsm.getState() == TCP_S_SYN_SENT) && ((int)state->syn_rexmit_count <= synLinearTimeouts);
+ if (!linearTimeout)
+ state->syn_rexmit_timeout *= 2;
+
+ // the configured RTO ceiling caps handshake retransmits too (Linux
+ // net.ipv4.tcp_rto_max_ms bounds the SYN-ACK backoff; tcp_rto_synack_rto_max
+ // pins 1s-spaced SYN-ACK retransmits under a 1s cap)
+ simtime_t maxSynRexmitTimeout = tcpMain->par("maxRexmitTimeout");
+ if (maxSynRexmitTimeout > TCP_TIMEOUT_SYN_REXMIT_MAX)
+ maxSynRexmitTimeout = TCP_TIMEOUT_SYN_REXMIT_MAX;
+ if (state->syn_rexmit_timeout > maxSynRexmitTimeout)
+ state->syn_rexmit_timeout = maxSynRexmitTimeout;
scheduleAfter(state->syn_rexmit_timeout, synRexmitTimer);
}
diff --git a/src/inet/transportlayer/tcp/TcpConnectionSackUtil.cc b/src/inet/transportlayer/tcp/TcpConnectionSackUtil.cc
deleted file mode 100644
index 6c2aa693240..00000000000
--- a/src/inet/transportlayer/tcp/TcpConnectionSackUtil.cc
+++ /dev/null
@@ -1,650 +0,0 @@
-//
-// Copyright (C) 2004 OpenSim Ltd.
-// Copyright (C) 2009-2011 Thomas Reschka
-// Copyright (C) 2011 OpenSim Ltd.
-//
-// SPDX-License-Identifier: LGPL-3.0-or-later
-//
-
-#include
-
-#include // min,max
-
-#include "inet/transportlayer/contract/tcp/TcpCommand_m.h"
-#include "inet/transportlayer/tcp/Tcp.h"
-#include "inet/transportlayer/tcp/TcpAlgorithm.h"
-#include "inet/transportlayer/tcp/TcpConnection.h"
-#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
-#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
-#include "inet/transportlayer/tcp/TcpSendQueue.h"
-#include "inet/transportlayer/tcp_common/TcpHeader.h"
-
-namespace inet {
-namespace tcp {
-
-//
-// helper functions for SACK
-//
-
-bool TcpConnection::processSACKOption(const Ptr& tcpHeader, const TcpOptionSack& option)
-{
- if (option.getLength() % 8 != 2) {
- EV_ERROR << "ERROR: option length incorrect\n";
- return false;
- }
-
- uint n = option.getSackItemArraySize();
- ASSERT(option.getLength() == 2 + n * 8);
-
- if (!state->sack_enabled) {
- EV_ERROR << "ERROR: " << n << " SACK(s) received, but sack_enabled is set to false\n";
- return false;
- }
-
- if (fsm.getState() != TCP_S_SYN_RCVD && fsm.getState() != TCP_S_ESTABLISHED
- && fsm.getState() != TCP_S_FIN_WAIT_1 && fsm.getState() != TCP_S_FIN_WAIT_2)
- {
- EV_ERROR << "ERROR: Tcp Header Option SACK received, but in unexpected state\n";
- return false;
- }
-
- if (n > 0) { // sacks present?
- EV_INFO << n << " SACK(s) received:\n";
- for (uint i = 0; i < n; i++) {
- Sack tmp;
- tmp.setStart(option.getSackItem(i).getStart());
- tmp.setEnd(option.getSackItem(i).getEnd());
-
- EV_INFO << (i + 1) << ". SACK: " << tmp.str() << endl;
-
- // check for D-SACK
- if (i == 0 && seqLE(tmp.getEnd(), tcpHeader->getAckNo())) {
- // RFC 2883, page 8:
- // "In order for the sender to check that the first (D)SACK block of an
- // acknowledgement in fact acknowledges duplicate data, the sender
- // should compare the sequence space in the first SACK block to the
- // cumulative ACK which is carried IN THE SAME PACKET. If the SACK
- // sequence space is less than this cumulative ACK, it is an indication
- // that the segment identified by the SACK block has been received more
- // than once by the receiver. An implementation MUST NOT compare the
- // sequence space in the SACK block to the TCP state variable snd.una
- // (which carries the total cumulative ACK), as this may result in the
- // wrong conclusion if ACK packets are reordered."
- EV_DETAIL << "Received D-SACK below cumulative ACK=" << tcpHeader->getAckNo()
- << " D-SACK: " << tmp.str() << endl;
- // Note: RFC 2883 does not specify what should be done in this case.
- // RFC 2883, page 9:
- // "5. Detection of Duplicate Packets
- // (...) This document does not specify what action a TCP implementation should
- // take in these cases. The extension to the SACK option simply enables
- // the sender to detect each of these cases.(...)"
- }
- else if (i == 0 && n > 1 && seqGreater(tmp.getEnd(), tcpHeader->getAckNo())) {
- // RFC 2883, page 8:
- // "If the sequence space in the first SACK block is greater than the
- // cumulative ACK, then the sender next compares the sequence space in
- // the first SACK block with the sequence space in the second SACK
- // block, if there is one. This comparison can determine if the first
- // SACK block is reporting duplicate data that lies above the cumulative
- // ACK."
- Sack tmp2(option.getSackItem(1).getStart(), option.getSackItem(1).getEnd());
-
- if (tmp2.contains(tmp)) {
- EV_DETAIL << "Received D-SACK above cumulative ACK=" << tcpHeader->getAckNo()
- << " D-SACK: " << tmp.str()
- << ", SACK: " << tmp2.str() << endl;
- // Note: RFC 2883 does not specify what should be done in this case.
- // RFC 2883, page 9:
- // "5. Detection of Duplicate Packets
- // (...) This document does not specify what action a TCP implementation should
- // take in these cases. The extension to the SACK option simply enables
- // the sender to detect each of these cases.(...)"
- }
- }
-
- if (seqGreater(tmp.getEnd(), tcpHeader->getAckNo()) && seqGreater(tmp.getEnd(), state->snd_una))
- rexmitQueue->setSackedBit(tmp.getStart(), tmp.getEnd());
- else
- EV_DETAIL << "Received SACK below total cumulative ACK snd_una=" << state->snd_una << "\n";
- }
- state->rcv_sacks += n; // total counter, no current number
-
- emit(rcvSacksSignal, state->rcv_sacks);
-
- // update scoreboard
- state->sackedBytes_old = state->sackedBytes; // needed for RFC 3042 to check if last dupAck contained new sack information
- state->sackedBytes = rexmitQueue->getTotalAmountOfSackedBytes();
-
- emit(sackedBytesSignal, state->sackedBytes);
- }
- return true;
-}
-
-bool TcpConnection::isLost(uint32_t seqNum)
-{
- ASSERT(state->sack_enabled);
-
- // RFC 3517, page 3: "This routine returns whether the given sequence number is
- // considered to be lost. The routine returns true when either
- // DupThresh discontiguous SACKed sequences have arrived above
- // 'SeqNum' or (DupThresh * SMSS) bytes with sequence numbers greater
- // than 'SeqNum' have been SACKed. Otherwise, the routine returns
- // false."
- ASSERT(seqGE(seqNum, state->snd_una)); // HighAck = snd_una
-
- bool isLost = (rexmitQueue->getNumOfDiscontiguousSacks(seqNum) >= state->dupthresh
- || rexmitQueue->getAmountOfSackedBytes(seqNum) >= (state->dupthresh * state->snd_mss));
-
- return isLost;
-}
-
-void TcpConnection::setPipe()
-{
- ASSERT(state->sack_enabled);
-
- // RFC 3517, pages 1 and 2: "
- // "HighACK" is the sequence number of the highest byte of data that
- // has been cumulatively ACKed at a given point.
- //
- // "HighData" is the highest sequence number transmitted at a given
- // point.
- //
- // "HighRxt" is the highest sequence number which has been
- // retransmitted during the current loss recovery phase.
- //
- // "Pipe" is a sender's estimate of the number of bytes outstanding
- // in the network. This is used during recovery for limiting the
- // sender's sending rate. The pipe variable allows TCP to use a
- // fundamentally different congestion control than specified in
- // [RFC2581]. The algorithm is often referred to as the "pipe
- // algorithm"."
- // HighAck = snd_una
- // HighData = snd_max
-
- state->highRxt = rexmitQueue->getHighestRexmittedSeqNum();
- state->pipe = 0;
- uint32_t length = 0; // required for rexmitQueue->checkSackBlock()
- bool sacked; // required for rexmitQueue->checkSackBlock()
- bool rexmitted; // required for rexmitQueue->checkSackBlock()
-
- // RFC 3517, page 3: "This routine traverses the sequence space from HighACK to HighData
- // and MUST set the "pipe" variable to an estimate of the number of
- // octets that are currently in transit between the TCP sender and
- // the TCP receiver. After initializing pipe to zero the following
- // steps are taken for each octet 'S1' in the sequence space between
- // HighACK and HighData that has not been SACKed:"
- for (uint32_t s1 = state->snd_una; seqLess(s1, state->snd_max); s1 += length) {
- rexmitQueue->checkSackBlock(s1, length, sacked, rexmitted);
-
- if (!sacked) {
- // RFC 3517, page 3: "(a) If IsLost (S1) returns false:
- //
- // Pipe is incremented by 1 octet.
- //
- // The effect of this condition is that pipe is incremented for
- // packets that have not been SACKed and have not been determined
- // to have been lost (i.e., those segments that are still assumed
- // to be in the network)."
- if (isLost(s1) == false)
- state->pipe += length;
-
- // RFC 3517, pages 3 and 4: "(b) If S1 <= HighRxt:
- //
- // Pipe is incremented by 1 octet.
- //
- // The effect of this condition is that pipe is incremented for
- // the retransmission of the octet.
- //
- // Note that octets retransmitted without being considered lost are
- // counted twice by the above mechanism."
- if (seqLess(s1, state->highRxt))
- state->pipe += length;
- }
- }
-
- emit(pipeSignal, state->pipe);
-}
-
-bool TcpConnection::nextSeg(uint32_t& seqNum)
-{
- ASSERT(state->sack_enabled);
-
- // RFC 3517, page 5: "This routine uses the scoreboard data structure maintained by the
- // Update() function to determine what to transmit based on the SACK
- // information that has arrived from the data receiver (and hence
- // been marked in the scoreboard). NextSeg () MUST return the
- // sequence number range of the next segment that is to be
- // transmitted, per the following rules:"
-
- state->highRxt = rexmitQueue->getHighestRexmittedSeqNum();
- uint32_t highestSackedSeqNum = rexmitQueue->getHighestSackedSeqNum();
- uint32_t shift = state->snd_mss;
- bool sacked = false; // required for rexmitQueue->checkSackBlock()
- bool rexmitted = false; // required for rexmitQueue->checkSackBlock()
-
- seqNum = 0;
-
- if (state->ts_enabled)
- shift -= TCP_OPTION_TS_SIZE.get();
-
- // RFC 3517, page 5: "(1) If there exists a smallest unSACKed sequence number 'S2' that
- // meets the following three criteria for determining loss, the
- // sequence range of one segment of up to SMSS octets starting
- // with S2 MUST be returned.
- //
- // (1.a) S2 is greater than HighRxt.
- //
- // (1.b) S2 is less than the highest octet covered by any
- // received SACK.
- //
- // (1.c) IsLost (S2) returns true."
-
- // Note: state->highRxt == RFC.HighRxt + 1
- for (uint32_t s2 = state->highRxt;
- seqLess(s2, state->snd_max) && seqLess(s2, highestSackedSeqNum);
- s2 += shift)
- {
- rexmitQueue->checkSackBlock(s2, shift, sacked, rexmitted);
-
- if (!sacked) {
- if (isLost(s2)) { // 1.a and 1.b are true, see above "for" statement
- seqNum = s2;
-
- return true;
- }
-
- break; // !isLost(x) --> !isLost(x + d)
- }
- }
-
- // RFC 3517, page 5: "(2) If no sequence number 'S2' per rule (1) exists but there
- // exists available unsent data and the receiver's advertised
- // window allows, the sequence range of one segment of up to SMSS
- // octets of previously unsent data starting with sequence number
- // HighData+1 MUST be returned."
- {
- // check how many unsent bytes we have
- uint32_t buffered = sendQueue->getBytesAvailable(state->snd_max);
- uint32_t maxWindow = state->snd_wnd;
- // effectiveWindow: number of bytes we're allowed to send now
- uint32_t effectiveWin = maxWindow - state->pipe;
-
- if (buffered > 0 && effectiveWin >= state->snd_mss) {
- seqNum = state->snd_max; // HighData = snd_max
-
- return true;
- }
- }
-
- // RFC 3517, pages 5 and 6: "(3) If the conditions for rules (1) and (2) fail, but there exists
- // an unSACKed sequence number 'S3' that meets the criteria for
- // detecting loss given in steps (1.a) and (1.b) above
- // (specifically excluding step (1.c)) then one segment of up to
- // SMSS octets starting with S3 MAY be returned.
- //
- // Note that rule (3) is a sort of retransmission "last resort".
- // It allows for retransmission of sequence numbers even when the
- // sender has less certainty a segment has been lost than as with
- // rule (1). Retransmitting segments via rule (3) will help
- // sustain TCP's ACK clock and therefore can potentially help
- // avoid retransmission timeouts. However, in sending these
- // segments the sender has two copies of the same data considered
- // to be in the network (and also in the Pipe estimate). When an
- // ACK or SACK arrives covering this retransmitted segment, the
- // sender cannot be sure exactly how much data left the network
- // (one of the two transmissions of the packet or both
- // transmissions of the packet). Therefore the sender may
- // underestimate Pipe by considering both segments to have left
- // the network when it is possible that only one of the two has.
- //
- // We believe that the triggering of rule (3) will be rare and
- // that the implications are likely limited to corner cases
- // relative to the entire recovery algorithm. Therefore we leave
- // the decision of whether or not to use rule (3) to
- // implementors."
- {
- for (uint32_t s3 = state->highRxt;
- seqLess(s3, state->snd_max) && seqLess(s3, highestSackedSeqNum);
- s3 += shift)
- {
- rexmitQueue->checkSackBlock(s3, shift, sacked, rexmitted);
-
- if (!sacked) {
- // 1.a and 1.b are true, see above "for" statement
- seqNum = s3;
-
- return true;
- }
- }
- }
-
- // RFC 3517, page 6: "(4) If the conditions for each of (1), (2), and (3) are not met,
- // then NextSeg () MUST indicate failure, and no segment is
- // returned."
- seqNum = 0;
-
- return false;
-}
-
-void TcpConnection::sendDataDuringLossRecoveryPhase(uint32_t congestionWindow)
-{
- ASSERT(state->sack_enabled && state->lossRecovery);
-
- // RFC 3517 pages 7 and 8: "(5) In order to take advantage of potential additional available
- // cwnd, proceed to step (C) below.
- // (...)
- // (C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more
- // segments as follows:
- // (...)
- // (C.5) If cwnd - pipe >= 1 SMSS, return to (C.1)"
- while (((int)congestionWindow - (int)state->pipe) >= (int)state->snd_mss) { // Note: Typecast needed to avoid prohibited transmissions
- // RFC 3517 pages 7 and 8: "(C.1) The scoreboard MUST be queried via NextSeg () for the
- // sequence number range of the next segment to transmit (if any),
- // and the given segment sent. If NextSeg () returns failure (no
- // data to send) return without sending anything (i.e., terminate
- // steps C.1 -- C.5)."
-
- uint32_t seqNum;
-
- if (!nextSeg(seqNum)) // if nextSeg() returns false (=failure): terminate steps C.1 -- C.5
- break;
-
- uint32_t sentBytes = sendSegmentDuringLossRecoveryPhase(seqNum);
- // RFC 3517 page 8: "(C.4) The estimate of the amount of data outstanding in the
- // network must be updated by incrementing pipe by the number of
- // octets transmitted in (C.1)."
- state->pipe += sentBytes;
- }
-}
-
-uint32_t TcpConnection::sendSegmentDuringLossRecoveryPhase(uint32_t seqNum)
-{
- ASSERT(state->sack_enabled && state->lossRecovery);
-
- // start sending from seqNum
- state->snd_nxt = seqNum;
-
- uint32_t old_highRxt = rexmitQueue->getHighestRexmittedSeqNum();
-
- // no need to check cwnd and rwnd - has already be done before
- // no need to check nagle - sending mss bytes
- uint32_t sentBytes = sendSegment(state->snd_mss);
-
- uint32_t sentSeqNum = seqNum + sentBytes;
-
- if (state->send_fin && sentSeqNum == state->snd_fin_seq)
- sentSeqNum = sentSeqNum + 1;
-
- ASSERT(seqLE(state->snd_nxt, sentSeqNum));
-
- // RFC 3517 page 8: "(C.2) If any of the data octets sent in (C.1) are below HighData,
- // HighRxt MUST be set to the highest sequence number of the
- // retransmitted segment."
- if (seqLess(seqNum, state->snd_max)) { // HighData = snd_max
- state->highRxt = rexmitQueue->getHighestRexmittedSeqNum();
- }
-
- // RFC 3517 page 8: "(C.3) If any of the data octets sent in (C.1) are above HighData,
- // HighData must be updated to reflect the transmission of
- // previously unsent data."
- if (seqGreater(sentSeqNum, state->snd_max)) // HighData = snd_max
- state->snd_max = sentSeqNum;
-
- emit(unackedSignal, state->snd_max - state->snd_una);
-
- // RFC 3517, page 9: "6 Managing the RTO Timer
- //
- // The standard TCP RTO estimator is defined in [RFC2988]. Due to the
- // fact that the SACK algorithm in this document can have an impact on
- // the behavior of the estimator, implementers may wish to consider how
- // the timer is managed. [RFC2988] calls for the RTO timer to be
- // re-armed each time an ACK arrives that advances the cumulative ACK
- // point. Because the algorithm presented in this document can keep the
- // ACK clock going through a fairly significant loss event,
- // (comparatively longer than the algorithm described in [RFC2581]), on
- // some networks the loss event could last longer than the RTO. In this
- // case the RTO timer would expire prematurely and a segment that need
- // not be retransmitted would be resent.
- //
- // Therefore we give implementers the latitude to use the standard
- // [RFC2988] style RTO management or, optionally, a more careful variant
- // that re-arms the RTO timer on each retransmission that is sent during
- // recovery MAY be used. This provides a more conservative timer than
- // specified in [RFC2988], and so may not always be an attractive
- // alternative. However, in some cases it may prevent needless
- // retransmissions, go-back-N transmission and further reduction of the
- // congestion window."
- tcpAlgorithm->ackSent();
-
- if (old_highRxt != state->highRxt) {
- // Note: Restart of REXMIT timer on retransmission is not part of RFC 2581, however optional in RFC 3517 if sent during recovery.
- EV_INFO << "Retransmission sent during recovery, restarting REXMIT timer.\n";
- tcpAlgorithm->restartRexmitTimer();
- }
- else // don't measure RTT for retransmitted packets
- tcpAlgorithm->dataSent(seqNum); // seqNum = old_snd_nxt
-
- return sentBytes;
-}
-
-TcpHeader TcpConnection::addSacks(const Ptr& tcpHeader)
-{
- B options_len = B(0);
- B used_options_len = tcpHeader->getHeaderOptionArrayLength();
- bool dsack_inserted = false; // set if dsack is subsets of a bigger sack block recently reported
-
- uint32_t start = state->start_seqno;
- uint32_t end = state->end_seqno;
-
- // delete old sacks (below rcv_nxt), delete duplicates and print previous status of sacks_array:
- auto it = state->sacks_array.begin();
- EV_INFO << "Previous status of sacks_array: \n" << ((it != state->sacks_array.end()) ? "" : "\t EMPTY\n");
-
- while (it != state->sacks_array.end()) {
- if (seqLE(it->getEnd(), state->rcv_nxt) || it->empty()) {
- EV_DETAIL << "\t SACK in sacks_array: " << " " << it->str() << " delete now\n";
- it = state->sacks_array.erase(it);
- }
- else {
- EV_DETAIL << "\t SACK in sacks_array: " << " " << it->str() << endl;
-
- ASSERT(seqGE(it->getStart(), state->rcv_nxt));
-
- it++;
- }
- }
-
- if (used_options_len > TCP_OPTIONS_MAX_SIZE - TCP_OPTION_SACK_MIN_SIZE) {
- EV_ERROR << "ERROR: Failed to addSacks - at least 10 free bytes needed for SACK - used_options_len=" << used_options_len << endl;
-
- // reset flags:
- state->snd_sack = false;
- state->snd_dsack = false;
- state->start_seqno = 0;
- state->end_seqno = 0;
- return *tcpHeader;
- }
-
- if (start != end) {
- if (state->snd_dsack) { // SequenceNo < rcv_nxt
- // RFC 2883, page 3:
- // "(3) The left edge of the D-SACK block specifies the first sequence
- // number of the duplicate contiguous sequence, and the right edge of
- // the D-SACK block specifies the sequence number immediately following
- // the last sequence in the duplicate contiguous sequence."
- if (seqLess(start, state->rcv_nxt) && seqLess(state->rcv_nxt, end))
- end = state->rcv_nxt;
-
- dsack_inserted = true;
- Sack nSack(start, end);
- state->sacks_array.push_front(nSack);
- EV_DETAIL << "inserted DSACK entry: " << nSack.str() << "\n";
- }
- else {
- uint32_t contStart = receiveQueue->getLE(start);
- uint32_t contEnd = receiveQueue->getRE(end);
-
- Sack newSack(contStart, contEnd);
- state->sacks_array.push_front(newSack);
- EV_DETAIL << "Inserted SACK entry: " << newSack.str() << "\n";
- }
-
- // RFC 2883, page 3:
- // "(3) The left edge of the D-SACK block specifies the first sequence
- // number of the duplicate contiguous sequence, and the right edge of
- // the D-SACK block specifies the sequence number immediately following
- // the last sequence in the duplicate contiguous sequence."
-
- // RFC 2018, page 4:
- // "* The first SACK block (i.e., the one immediately following the
- // kind and length fields in the option) MUST specify the contiguous
- // block of data containing the segment which triggered this ACK,
- // unless that segment advanced the Acknowledgment Number field in
- // the header. This assures that the ACK with the SACK option
- // reflects the most recent change in the data receiver's buffer
- // queue."
-
- // RFC 2018, page 4:
- // "* The first SACK block (i.e., the one immediately following the
- // kind and length fields in the option) MUST specify the contiguous
- // block of data containing the segment which triggered this ACK,"
-
- // RFC 2883, page 3:
- // "(4) If the D-SACK block reports a duplicate contiguous sequence from
- // a (possibly larger) block of data in the receiver's data queue above
- // the cumulative acknowledgement, then the second SACK block in that
- // SACK option should specify that (possibly larger) block of data.
- //
- // (5) Following the SACK blocks described above for reporting duplicate
- // segments, additional SACK blocks can be used for reporting additional
- // blocks of data, as specified in RFC 2018."
-
- // RFC 2018, page 4:
- // "* The SACK option SHOULD be filled out by repeating the most
- // recently reported SACK blocks (based on first SACK blocks in
- // previous SACK options) that are not subsets of a SACK block
- // already included in the SACK option being constructed."
-
- it = state->sacks_array.begin();
- if (dsack_inserted)
- it++;
-
- for (; it != state->sacks_array.end(); it++) {
- ASSERT(!it->empty());
-
- auto it2 = it;
- it2++;
- while (it2 != state->sacks_array.end()) {
- if (it->contains(*it2)) {
- EV_DETAIL << "sack matched, delete contained : a=" << it->str() << ", b=" << it2->str() << endl;
- it2 = state->sacks_array.erase(it2);
- }
- else
- it2++;
- }
- }
- }
-
- uint n = state->sacks_array.size();
-
- uint maxnode = (((TCP_OPTIONS_MAX_SIZE - used_options_len).get()) - 2) / 8; // 2: option header, 8: size of one sack entry
-
- if (n > maxnode)
- n = maxnode;
-
- if (n == 0) {
- if (dsack_inserted)
- state->sacks_array.pop_front(); // delete DSACK entry
-
- // reset flags:
- state->snd_sack = false;
- state->snd_dsack = false;
- state->start_seqno = 0;
- state->end_seqno = 0;
-
- return *tcpHeader;
- }
-
- uint optArrSize = tcpHeader->getHeaderOptionArraySize();
-
- uint optArrSizeAligned = optArrSize;
-
- while (used_options_len.get() % 4 != 2) {
- used_options_len++;
- optArrSizeAligned++;
- }
-
- while (optArrSize < optArrSizeAligned) {
- tcpHeader->appendHeaderOption(new TcpOptionNop());
- optArrSize++;
- }
-
- ASSERT(used_options_len.get() % 4 == 2);
-
- TcpOptionSack *option = new TcpOptionSack();
- option->setLength(8 * n + 2);
- option->setSackItemArraySize(n);
-
- // write sacks from sacks_array to options
- uint counter = 0;
-
- for (it = state->sacks_array.begin(); it != state->sacks_array.end() && counter < n; it++) {
- ASSERT(it->getStart() != it->getEnd());
- option->setSackItem(counter++, *it);
- }
-
- // independent of "n" we always need 2 padding bytes (NOP) to make: (used_options_len % 4 == 0)
- options_len = used_options_len + TCP_OPTION_SACK_ENTRY_SIZE * n + TCP_OPTION_HEAD_SIZE; // 8 bytes for each SACK (n) + 2 bytes for kind&length
-
- ASSERT(options_len <= TCP_OPTIONS_MAX_SIZE); // Options length allowed? - maximum: 40 Bytes
-
- tcpHeader->appendHeaderOption(option);
- tcpHeader->setHeaderLength(TCP_MIN_HEADER_LENGTH + tcpHeader->getHeaderOptionArrayLength());
- tcpHeader->setChunkLength(tcpHeader->getHeaderLength());
- // update number of sent sacks
- state->snd_sacks += n;
-
- emit(sndSacksSignal, state->snd_sacks);
-
- EV_INFO << n << " SACK(s) added to header:\n";
-
- for (uint t = 0; t < n; t++) {
- EV_INFO << t << ". SACK:" << " [" << option->getSackItem(t).getStart() << ".." << option->getSackItem(t).getEnd() << ")";
-
- if (t == 0) {
- if (state->snd_dsack)
- EV_INFO << " (D-SACK)";
- else if (seqLE(option->getSackItem(t).getEnd(), state->rcv_nxt)) {
- EV_INFO << " (received segment filled out a gap)";
- state->snd_dsack = true; // Note: Set snd_dsack to delete first sack from sacks_array
- }
- }
-
- EV_INFO << endl;
- }
-
- // RFC 2883, page 3:
- // "(1) A D-SACK block is only used to report a duplicate contiguous
- // sequence of data received by the receiver in the most recent packet.
- //
- // (2) Each duplicate contiguous sequence of data received is reported
- // in at most one D-SACK block. (I.e., the receiver sends two identical
- // D-SACK blocks in subsequent packets only if the receiver receives two
- // duplicate segments.)//
- //
- // In case of d-sack: delete first sack (d-sack) and move old sacks by one to the left
- if (dsack_inserted)
- state->sacks_array.pop_front(); // delete DSACK entry
-
- // reset flags:
- state->snd_sack = false;
- state->snd_dsack = false;
- state->start_seqno = 0;
- state->end_seqno = 0;
-
- return *tcpHeader;
-}
-
-} // namespace tcp
-} // namespace inet
-
diff --git a/src/inet/transportlayer/tcp/TcpConnectionState.msg b/src/inet/transportlayer/tcp/TcpConnectionState.msg
index f35473dad5e..43fb87ae042 100644
--- a/src/inet/transportlayer/tcp/TcpConnectionState.msg
+++ b/src/inet/transportlayer/tcp/TcpConnectionState.msg
@@ -11,25 +11,27 @@ namespace inet::tcp;
cplusplus {{
typedef std::list SackList;
+typedef std::vector FastOpenCookie;
}}
class SackList { @existingClass; }
+class FastOpenCookie { @existingClass; }
//
// TCP FSM states
//
-// Brief descriptions (cf RFC 793, page 20):
+// Brief descriptions (cf RFC 9293, page 20):
//
-// LISTEN - waiting for a connection request
-// SYN-SENT - part of 3-way handshake (waiting for peer's SYN+ACK or SYN)
-// SYN-RECEIVED - part of 3-way handshake (we sent SYN too, waiting for it to be acked)
-// ESTABLISHED - normal data transfer
-// FIN-WAIT-1 - FIN sent, waiting for its ACK (or peer's FIN)
-// FIN-WAIT-2 - our side of the connection closed (our FIN acked), waiting for peer's FIN
-// CLOSE-WAIT - FIN received and acked, waiting for local user to close
-// LAST-ACK - remote side closed, FIN sent, waiting for its ACK
-// CLOSING - simultaneous close: sent FIN, then got peer's FIN
-// TIME-WAIT - both FIN's acked, waiting for some time to be sure remote TCP received our ACK
+// LISTEN - represents waiting for a connection request from any remote TCP peer and port.
+// SYN-SENT - represents waiting for a matching connection request after having sent a connection request.
+// SYN-RECEIVED - represents waiting for a confirming connection request acknowledgment after having both received and sent a connection request.
+// ESTABLISHED - represents an open connection, data received can be delivered to the user. The normal state for the data transfer phase of the connection.
+// FIN-WAIT-1 - represents waiting for a connection termination request from the remote TCP peer, or an acknowledgment of the connection termination request previously sent.
+// FIN-WAIT-2 - represents waiting for a connection termination request from the remote TCP peer.
+// CLOSE-WAIT - represents waiting for a connection termination request from the local user.
+// CLOSING - represents waiting for a connection termination request acknowledgment from the remote TCP peer.
+// LAST-ACK - represents waiting for an acknowledgment of the connection termination request previously sent to the remote TCP peer (this termination request sent to the remote TCP peer already included an acknowledgment of the termination request sent from the remote TCP peer).
+// TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP peer received the acknowledgment of its connection termination request and to avoid new connections being impacted by delayed segments from previous connections.
// CLOSED - represents no connection state at all.
//
// Note: FIN-WAIT-1, FIN-WAIT-2, CLOSING, TIME-WAIT represents active close (that is,
@@ -83,7 +85,7 @@ enum TcpEventCode {
TCP_E_RCV_UNEXP_SYN = 19; // unexpected SYN
// timers
- TCP_E_TIMEOUT_2MSL = 20; // RFC 793, a.k.a. TIME-WAIT timer
+ TCP_E_TIMEOUT_2MSL = 20; // RFC 9293, a.k.a. TIME-WAIT timer
TCP_E_TIMEOUT_CONN_ESTAB = 21;
TCP_E_TIMEOUT_FIN_WAIT_2 = 22;
@@ -100,7 +102,7 @@ enum TcpEventCode {
// it in Tkenv.)
//
// TcpStateVariables only contains variables needed to implement
-// the "base" (RFC 793) TCP. More advanced TCP variants are encapsulated
+// the "base" (RFC 9293) TCP. More advanced TCP variants are encapsulated
// into TcpAlgorithm subclasses which can have their own state blocks,
// subclassed from TcpStateVariables. See TcpAlgorithm::createStateVariables().
//
@@ -111,16 +113,24 @@ struct TcpStateVariables
bool active = false; // set if the connection was initiated by an active open
bool fork = false; // if passive and in LISTEN: whether to fork on an incoming connection
+ bool forked = false; // this connection WAS forked off a listener (durable, unlike listeningSocketId which is cleared on ACCEPT): on RST/hard-ICMP in SYN_RCVD it must close like a Linux child socket, never fall back to LISTEN (the original listener still exists -- falling back would duplicate it)
- uint32_t snd_mss = 0; // sender maximum segment size (without headers, i.e. only segment text); see RFC 2581, page 1.
+ uint32_t snd_mss = 0; // sender maximum segment size (without headers, i.e. only segment text); see RFC 5681.
+ uint32_t advertisedMss = 0; // our OWN maximum receivable segment size, as announced in this side's SYN/SYN-ACK MSS option
+ // (RFC 793/9293: the option describes the announcing side's receive limit). Unlike snd_mss it is
+ // NEVER clamped by the peer's announced MSS: a SYN-ACK announces this side's own receive
+ // limit, never the client's.
// This will be set to the minimum of the local smss parameter and the value specified in the
// MSS option received during connection setup.
+ uint32_t snd_effmss = 0; // effective sender maximum segment size without TCP/IP header options
- // send sequence number variables (see RFC 793, "3.2. Terminology")
+ // send sequence number variables (see RFC 9293, "3.3. TCP Terminology Overview")
uint32_t snd_una = 0; // send unacknowledged
uint32_t snd_nxt = 0; // send next (drops back on retransmission)
uint32_t snd_max = 0; // max seq number sent (needed because snd_nxt is re-set on retransmission)
uint32_t snd_wnd = 0; // send window
+ uint32_t max_window = 0; // largest peer window ever advertised (Linux tp->max_window); drives forced_push
+ uint32_t pushed_seq = 0; // highest seq already PSHed (Linux tp->pushed_seq); forced_push compares snd_nxt against it
uint32_t snd_up = 0; // send urgent pointer
uint32_t snd_wl1 = 0; // segment sequence number used for last window update
uint32_t snd_wl2 = 0; // segment ack. number used for last window update
@@ -131,12 +141,19 @@ struct TcpStateVariables
uint32_t rcv_wnd = 0; // receive window
uint32_t rcv_up = 0; // receive urgent pointer;
uint32_t irs = 0; // initial receive sequence number
- uint32_t rcv_adv = 0; // advertised window
+ uint32_t rcv_adv = 0; // right edge of the window CURRENTLY offered (Linux rcv_wup + rcv_wnd)
+ // The highest right edge EVER offered (Linux rcv_mwnd_seq). Distinct from
+ // rcv_adv, which tracks the offer in force and may be pulled back down -- e.g.
+ // when a buffer squeeze forces a zero window. Acceptance is judged against this
+ // maximum instead, because a peer that was once allowed to send that far may
+ // still have segments in flight from there (Linux tcp_max_receive_window).
+ uint32_t rcv_mwnd_seq = 0;
// SYN, SYN+ACK retransmission variables (handled separately
// because normal rexmit belongs to TcpAlgorithm)
int syn_rexmit_count = 0; // number of SYN/SYN+ACK retransmissions (=1 after first rexmit)
simtime_t syn_rexmit_timeout; // current SYN/SYN+ACK retransmission timeout
+ simtime_t handshakeSentTime = -1; // when our SYN (active) / SYN-ACK (passive) was last sent; seeds the RTT estimator from the handshake RTT on reaching ESTABLISHED (skipped per Karn if the handshake segment was retransmitted)
// whether ACK of our FIN has been received. Needed in FIN bit processing
// to decide between transition to TIME-WAIT and CLOSING (set event code
@@ -149,12 +166,30 @@ struct TcpStateVariables
bool fin_rcvd = false; // whether FIN received or not
uint32_t rcv_fin_seq = 0; // if fin_rcvd: sequence number of received FIN
- bool nagle_enabled = false; // set if Nagle's algorithm (RFC 896) is enabled
+ bool nagle_enabled = false; // set if Nagle's algorithm (RFC 1122) is enabled; also the runtime TCP_NODELAY switch (nodelay == !nagle_enabled)
+ // TCP_CORK / MSG_MORE ("corking"): hold the trailing sub-MSS partial segment;
+ // full-MSS segments always flow. TCP_CORK is persistent (until cleared); MSG_MORE
+ // is per-send (transient). See TcpConnection::flushCorkedData and the cork timer.
+ bool tcp_cork = false; // TCP_CORK: persistent partial-segment hold
+ bool msgMoreThisSend = false; // MSG_MORE on the current SEND: transient hold, read-and-cleared at the top of sendData()
+ bool corkedDataPending = false; // a cork/MSG_MORE partial is currently withheld (drives cork-timer arming and PSH gating)
+ bool pushHeldPartial = false; // the withheld partial must carry PSH when flushed (Linux tcp_mark_push on a non-MSG_MORE tail write)
+ bool corkFlush = false; // an explicit push (uncork / nodelay / timer) is in progress: bypass BOTH cork and Nagle holds for the trailing partial
+ bool forcePushHeld = false; // timer-path flush: force PSH on the flushed partial (Linux tcp_write_wakeup)
+ bool pushThisSegment = false; // transient handoff to sendSegment(): OR in PSH for the segment currently being flushed
+ bool pushOnWriteBoundary = false; // Linux parity: set PSH on the segment carrying the last buffered byte of a send (from the pushSegmentsOnWriteBoundary parameter)
bool delayed_acks_enabled = false; // set if delayed ACK algorithm (RFC 1122) is enabled
+ bool adaptiveDelayedAcks = false; // from the adaptiveDelayedAcks parameter: Linux-shaped quickack/ATO/pingpong receiver ACK dynamics
+ uint32_t quickAckCounter = 0; // remaining immediate-ACK budget (Linux icsk_ack.quick); consumed one per ACK sent
+ uint32_t pingpongCount = 0; // interactive-session evidence counter (Linux icsk_ack.pingpong); >= 3 = pingpong (delack-favoring) mode
+ simtime_t ackAto = 0; // adaptive delayed-ACK timeout (Linux icsk_ack.ato); 0 = engine not yet initialized (no data received)
+ simtime_t lastDataRecvTime = 0; // arrival time of the last in-order data segment (Linux icsk_ack.lrcvtime)
bool limited_transmit_enabled = false; // set if Limited Transmit algorithm (RFC 3042) is enabled
- bool increased_IW_enabled = false; // set if Increased Initial Window (RFC 3390) is enabled
+ bool increased_IW_enabled = false; // set if Increased Initial Window (RFC 3390) is enabled (deprecated, mapped to init_cwnd_mode)
+ int init_cwnd_mode = 0; // initial window: 0=RFC2001 (1 SMSS), 1=RFC3390, 2=RFC6928 (IW10)
uint32_t full_sized_segment_counter = 0; // this counter is needed for delayed ACK
+ uint32_t delayedAckFrameCount = 0; // number of frames after delayed acks are sent
bool ack_now = false; // send ACK immediately, needed if delayed_acks_enabled is set
// Based on [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 861].
// ack_now should be set when:
@@ -167,25 +202,27 @@ struct TcpStateVariables
bool afterRto = false; // set at RTO, reset when snd_nxt == snd_max or snd_una == snd_max
// WINDOW_SCALE related variables
- bool ws_support = false; // set if the host supports Window Scale (header option) (RFC 1323)
+ bool ws_support = false; // set if the host supports Window Scale (header option) (RFC 7323)
bool ws_enabled = false; // set if the connection uses Window Scale (header option)
int ws_manual_scale = -1; // the value of scale parameter if it was set manually (-1 otherwise)
bool snd_ws = false; // set if initial WINDOW_SCALE has been sent
bool rcv_ws = false; // set if initial WINDOW_SCALE has been received
- unsigned int rcv_wnd_scale = 0; // RFC 1323, page 31: "Receive window scale power"
- unsigned int snd_wnd_scale = 0; // RFC 1323, page 31: "Send window scale power"
+ unsigned int rcv_wnd_scale = 0; // RFC 7323, page 37: "Receive window scale exponent"
+ unsigned int snd_wnd_scale = 0; // RFC 7323, page 37: "Send window scale exponent"
// TIMESTAMP related variables
- bool ts_support = false; // set if the host supports Timestamps (header option) (RFC 1322)
+ bool ts_support = false; // set if the host supports Timestamps (header option) (RFC 1122)
bool ts_enabled = false; // set if the connection uses Window Scale (header option)
bool snd_initial_ts = false; // set if initial TIMESTAMP has been sent
bool rcv_initial_ts = false; // set if initial TIMESTAMP has been received
- uint32_t ts_recent = 0; // RFC 1323, page 31: "Latest received Timestamp"
- uint32_t last_ack_sent = 0; // RFC 1323, page 31: "Last ACK field sent"
+ uint32_t ts_recent = 0; // RFC 7323, page 37: "Latest received Timestamp"
+ uint32_t last_ack_sent = 0; // RFC 7323, page 37: "Last ACK field sent"
simtime_t time_last_data_sent; // time at which the last data segment was sent (needed to compute the IDLE time for PAWS)
+ simtime_t time_last_segment_received = 0; // time at which the last segment was received (idle base for keepalive)
// SACK related variables
- bool sack_support = false; // set if the host supports selective acknowledgment (header option) (RFC 2018, 2883, 3517)
+ bool sack_support = false; // set if the host supports selective acknowledgment (header option) (RFC 2018, 2883, 6675)
+ bool dsack_enabled = false; // set if the host supports dsack in sack option (header option) (RFC 2883)
bool sack_enabled = false; // set if the connection uses selective acknowledgment (header option)
bool snd_sack_perm = false; // set if SACK_PERMITTED has been sent
bool rcv_sack_perm = false; // set if SACK_PERMITTED has been received
@@ -194,17 +231,92 @@ struct TcpStateVariables
bool snd_sack = false; // set if received vaild out-of-order segment or rcv_nxt changed, but receivedQueue is not empty
bool snd_dsack = false; // set if received duplicated segment (sequenceNo+PLength < rcv_nxt) or (segment is not acceptable)
SackList sacks_array; // MAX_SACK_BLOCKS is set to 60
- uint32_t highRxt = 0; // RFC 3517, page 3: ""HighRxt" is the highest sequence number which has been retransmitted during the current loss recovery phase."
- uint32_t pipe = 0; // RFC 3517, page 3: ""Pipe" is a sender's estimate of the number of bytes outstanding in the network."
- uint32_t recoveryPoint = 0; // RFC 3517
+ uint32_t highRxt = 0; // RFC 6675, page 3: ""HighRxt" is the highest sequence number which has been retransmitted during the current loss recovery phase."
+ uint32_t pipe = 0; // RFC 6675, page 3: ""Pipe" is a sender's estimate of the number of bytes outstanding in the network."
+ uint32_t recoveryPoint = 0; // RFC 6675; snd_max when loss recovery started
uint32_t sackedBytes = 0; // number of sackedBytes
uint32_t sackedBytes_old = 0; // old number of sackedBytes - needed for RFC 3042 to check if last dupAck contained new sack information
bool lossRecovery = false; // indicates if algorithm is in loss recovery phase
+ // delivered-bytes accounting (RFC 8985 delivery; RFC 6937 PRR input)
+ uint64_t deliveredBytes = 0; // cumulative newly-delivered (cumulatively acked + selectively acked) bytes
+ uint64_t prrDeliveredMark = 0; // snapshot of deliveredBytes at the start of the current segment (per-ACK delta = newly acked+sacked)
+
+ // PRR - Proportional Rate Reduction (RFC 6937)
+ bool prrEnabled = false; // use PRR during fast recovery (set from the prrEnabled parameter)
+ uint32_t prrDelivered = 0; // bytes delivered during the current recovery episode
+ uint32_t prrOut = 0; // bytes transmitted during the current recovery episode
+ uint32_t priorCwnd = 0; // snd_cwnd snapshot at recovery entry (RFC 6937 RecoverFS proxy; also the undo target)
+
+ // loss undo (RFC 2883 D-SACK, RFC 3522 Eifel)
+ bool lossUndoEnabled = false; // set from the lossUndoEnabled parameter
+ uint32_t undoMarker = 0; // snd_una at recovery entry; 0 = no undo pending
+ int32_t undoRetrans = 0; // retransmitted segments still to be confirmed spurious (-1 = unknown)
+ uint32_t priorSsthresh = 0; // ssthresh before the reduction (restored on undo)
+ uint32_t retransStampTS = 0; // TSval echoed on the first retransmit of the episode (Eifel); 0 = none
+ uint32_t lastRcvdTSecr = 0; // most recent TSecr echoed back by the peer (Eifel input)
+
+ // Tail Loss Probe (RFC 8985 section 7.2, Linux tcp_send_loss_probe)
+ bool seedRttFromHandshake = false; // seed srtt/rttvar/RTO from the SYN<->SYN-ACK exchange, as Linux does
+ bool tlpEnabled = false; // Tail Loss Probe enabled (requires SACK)
+ uint32_t tlpHighSeq = 0; // snd_max when the probe was sent; nonzero = a probe is outstanding (Linux tlp_high_seq)
+ bool tlpRetrans = false; // the outstanding probe retransmitted the last segment (vs. sent new data)
+
+ // F-RTO spurious-RTO detection (RFC 5682, SACK-enhanced; Linux tcp_frto=2)
+ bool frtoEnabled = false; // from the frtoEnabled parameter; requires SACK
+ bool frtoActive = false; // an RTO fired and its spuriousness is still undecided
+ uint32_t frtoHighSeq = 0; // snd_max at the RTO (RFC 5682 recover / Linux high_seq)
+ bool frtoOrigAcked = false; // never-retransmitted data was (s)acked since the RTO
+
+ uint32_t maxPacketsOut = 0; // peak segments in flight observed at send time (Linux max_packets_out); drives the RFC 5681 cwnd-limited slow-start gate
+ // adaptive reordering / dynamic DupThresh (Linux tp->reordering)
+ bool adaptiveReorderingEnabled = false; // set from the adaptiveReorderingEnabled parameter
+ uint32_t reordering = 3; // learned reordering degree in segments; initialized to dupthresh
+ uint32_t maxReordering = 300; // upper bound (set from the maxReordering parameter)
+
+ bool dsackSeen = false; // a D-SACK was seen while processing the current segment (reset per segment)
+ uint32_t dsackBytes = 0; // bytes covered by the D-SACK seen on the current segment
+
+ // RACK-TLP loss detection (RFC 8985)
+ int lossDetectionMode = 0; // 0 = classic DupThresh (RFC 3517), 1 = RACK time-based
+ simtime_t minRtt = 0; // minimum RTT observed on the connection (0 = unset)
+ simtime_t rackXmitTime = 0; // send time of the most recently delivered (SACKed/ACKed) segment
+ uint32_t rackEndSeq = 0; // end sequence number of that segment
+ simtime_t rackRtt = 0; // RTT of the ACK that last advanced the RACK point
+ bool rackReordSeen = false; // whether reordering has been observed (widens the reordering window)
+
// queue management
uint32_t sendQueueLimit = 0;
bool queueUpdate = true;
+ // TCP_NOTSENT_LOWAT: independent of sendQueueLimit/queueUpdate
+ // above (which track total outstanding+unsent bytes from snd_una); this tracks
+ // only the not-yet-transmitted portion from snd_nxt. (uint32_t)-1 = disabled.
+ uint32_t notsentLowat = (uint32_t)-1;
+ bool notsentLowatUpdate = true;
+
+ // TCP_INFO trio (busy_time/rwnd_limited): cumulative time bookkeeping, read-only
+ // with respect to every send/receive decision -- consulted only by TcpStatusInfo.
+ // INET-native definitions (not a port of Linux's tcp_chrono_* state machine); see
+ // enqueueSendCommandData()/processAckInEstabEtc() (busy) and sendData() (rwnd).
+ // A *StartTime of -1 means "not currently in that state"; *Accumulated is the
+ // running total up to the last state exit. sndbuf_limited has no INET analog
+ // (the send queue never actually blocks a SEND on sendQueueLimit -- it's
+ // notification-only, see notsentLowat/sendQueueLimit above) and is deliberately
+ // not tracked here.
+ simtime_t busyStartTime; // set to -1 in configureStateVariables() -- see pmtudLastMssReduction below for the same pattern
+ simtime_t busyTimeAccumulated = 0;
+ simtime_t rwndLimitedStartTime; // set to -1 in configureStateVariables()
+ simtime_t rwndLimitedAccumulated = 0;
+ // Linux TCP_CHRONO_SNDBUF_LIMITED: time the TRANSMISSION was starved by
+ // the send buffer -- the send queue ran dry (everything queued is in
+ // flight) while the application writer was still blocked on buffer space
+ // (tcp_write_xmit's queue-empty + SOCK_NOSPACE start condition). The
+ // writer-blocked signal comes from the application layer via
+ // TcpSetWriterBlockedCommand (tcp-info-sndbuf-limited pins ~20ms).
+ simtime_t sndbufLimitedStartTime; // set to -1 in configureStateVariables()
+ simtime_t sndbufLimitedAccumulated = 0;
+
// those counters would logically belong to TcpAlgorithm, but it's a lot easier to manage them here
uint32_t dupacks = 0; // current number of received consecutive duplicate ACKs
uint32_t snd_sacks = 0; // number of sent sacks
@@ -223,21 +335,174 @@ struct TcpStateVariables
bool sndCwr = false; // set if ECE was handled
bool gotEce = false; // set if packet with ECE arrived
bool gotCeIndication = false; // set if CE was set in controlInfo from IP
- bool ect = false; // set if this connection is ECN Capable (ECT stands for ECN-Capable transport - rfc-3168)
+ bool ect = false; // set if this connection is ECN Capable (ECT stands for ECN-Capable transport, RFC 3168)
+ bool ecnMarkAll = false; // marks all packets, including pure ACKs, retransmissions, etc. with ECN
bool endPointIsWillingECN = false; // set if the other end-point is willing to use ECN
bool ecnSynSent = false; // set if ECN-setup SYN packet was sent
- bool ecnWillingness = false; // set if current host is willing to use ECN
+ bool ecnWillingness = false; // set if current host is willing to use ECN. Kept in sync with ecnMode>=2 ("rfc3168") for backward compatibility -- every classic-ECN call site still keys off this field
bool sndAck = false; // set if sending Ack packet, used to set relevant info in controlInfo.
- bool rexmit = false; // set if retransmitting data, used to send not-ECT codepoint (rfc3168, p. 20)
+ bool rexmit = false; // set if retransmitting data, used to send not-ECT codepoint (RFC 3168, page 20)
simtime_t eceReactionTime; // records the time of the last ECE reaction
- uint32_t dupthresh = 0; // used for TcpTahoe, TcpReno and SACK (RFC 3517)
+ // AccECN (draft-ietf-tcpm-accurate-ecn)
+ int ecnMode = 0; // from the tcpEcnMode parameter (or the deprecated ecnWillingness, mapped): 0=off, 1=passive, 2=rfc3168, 3=accecn, 4=accecn-passive
+ bool accEcnNegotiated = false; // set once 3WHS negotiation determines this connection uses AccECN, not classic ECN or no-ECN
+ bool aeSynSent = false; // set if an AccECN-requesting SYN (the flag-bit combination) was sent
+ uint32_t rcvCePkts = 0; // receiver-side count of CE-marked packets seen (mod-8 ACE counter input)
+ uint32_t rcvCePktsReported = 0; // rcvCePkts value as of the last ACE delta the peer has acknowledged resolving
+ uint32_t deliveredCePkts = 0; // sender-side resolved count of CE-marked packets the peer has reported via ACE/option
+
+ // AccECN ECN-field reflector (RFC 9768 section 3.2.3.2, table "Encoding of the ACE field on the SYN-ACK / third ACK"):
+ // the handshake feeds the IP-ECN codepoint of the SYN back on the SYN-ACK, and that of the SYN-ACK back on the
+ // handshake-completing ACK, so each end learns whether the network mangled the ECN field of the packet it sent.
+ // accEcnReflectCodepoint holds the IP-ECN codepoint (IP_ECN_*) of the received SYN (server) or SYN-ACK (client),
+ // captured where the EcnInd tag is still readable; accEcnReflectAce arms the ONE ACK that must carry the reflected
+ // value instead of the ordinary mod-8 CE counter (the reflection is a handshake one-off; everything after it is a counter).
+ int accEcnReflectCodepoint = -1;
+ bool accEcnReflectAce = false;
+
+ // AccECN TCP option (kind 172/174 E0B/E1B/CEB byte counters)
+ bool accEcnOptionEnabled = false; // cached from the accEcnOptionEnabled param at configureStateVariables() time
+ uint32_t accEcnOptionBeaconAcks = 4; // cached from the accEcnOptionBeaconAcks param: emit the option on every Nth ACK-bearing segment
+ uint32_t accEcnAckCount = 0; // counts ACK-bearing segments sent since AccECN negotiated, for the beacon cadence above
+ bool accEcnOptionNextKindIsAccEcn1 = false; // alternates which of kind 172/174 is emitted each time the option is actually sent
+ bool accEcnOptionKindAlternates = true; // cached from the accEcnOptionKindAlternates param: if false, always emit kind 174 (Linux behavior) instead of alternating
+ uint32_t rcvEct0Bytes = 0; // receiver-side byte count of segments arriving marked ECT(0) since AccECN negotiated (option's E0B field content)
+ uint32_t rcvEct1Bytes = 0; // receiver-side byte count of segments arriving marked ECT(1) since AccECN negotiated (option's E1B field content)
+ uint32_t rcvCeBytes = 0; // receiver-side byte count of segments arriving marked CE since AccECN negotiated (option's CEB field content)
+
+ // AccECN TCP option read side: peer-reported byte counts, decoded from the last
+ // AccECN option this connection has received (offsets already un-applied). These are
+ // PEER-reported feedback on bytes WE sent (opposite direction from rcvEct*Bytes/rcvCeBytes
+ // above, which are what WE observed on bytes the peer sent us).
+ uint32_t peerReportedEct0Bytes = 0;
+ uint32_t peerReportedEct1Bytes = 0;
+ // peerReportedCeBytes is the CEB baseline, advanced only at the point its delta is
+ // actually consumed (processAckInEstabEtc()'s ACE block) -- NOT at decode time
+ // (readHeaderOptions() below), because that block has an early-return path (an ACK
+ // beyond snd_max) that would otherwise silently lose the delta: if the baseline had
+ // already advanced but the block never ran to fold the delta into deliveredCeBytes,
+ // those CE bytes would be gone for good, and the next real option would diff against
+ // an already-advanced baseline as if they'd been accounted for. The write side keeps
+ // the same read-vs-mutate separation (dry-run calls there, an early return here).
+ uint32_t peerReportedCeBytes = 0; // cumulative (peer's clock); 0 is a legitimate starting baseline, not a sentinel -- CEB itself starts at 0
+ bool accEcnOptionCebDeltaValid = false; // true only for the segment just processed by readHeaderOptions(), if it carried a valid AccECN option
+ uint32_t accEcnOptionRawCeBytes = 0; // valid only when accEcnOptionCebDeltaValid: the raw (offset-corrected, but not yet diffed) CEB value decoded from that segment's option
+ uint32_t deliveredCeBytes = 0; // sender-side resolved CE byte count from option evidence only; stays 0 if the peer never sends the option
+ // ECT0/ECT1 delivered-byte accounting, exactly mirroring the CEB machinery above.
+ // peerReportedEct0Bytes/Ect1Bytes are the (offset-corrected) baselines, advanced only
+ // when each delta is consumed in the ACE block. The raw/valid pair is decoded per
+ // segment by readHeaderOptions(). deliveredE0Bytes/E1Bytes are the cumulative totals
+ // reported as tcpi_delivered_e0_bytes / tcpi_delivered_e1_bytes.
+ bool accEcnOptionE0DeltaValid = false;
+ bool accEcnOptionE1DeltaValid = false;
+ uint32_t accEcnOptionRawE0Bytes = 0;
+ uint32_t accEcnOptionRawE1Bytes = 0;
+ uint32_t deliveredE0Bytes = 0;
+ uint32_t deliveredE1Bytes = 0;
+ // Linux tp->saw_accecn_opt: the peer has sent an AccECN TCP option at least once.
+ // Gates OUR option emission on established segments (tcp_established_options
+ // sends the option only to peers that demonstrated they use it; a peer that
+ // never sends one gets pure ACE-field feedback -- accecn tsprogress/tsnoprogress
+ // pin data segments WITHOUT the option after an option-less handshake).
+ // set by a FULL close (TCP_C_CLOSE without halfClose): the application will
+ // never read again, so new data arriving in FIN_WAIT_1/2 resets the
+ // connection (RFC 1122 4.2.2.13 / Linux RCV_SHUTDOWN + TCPABORTONDATA).
+ // A shutdown(SHUT_WR)-style half close leaves it false.
+ bool rcvShutdown = false;
+ // receive-BUFFER capacity (Linux sk_rcvbuf), decoupled from maxRcvBuffer /
+ // the advertised window when the receiveBufferSize parameter is set;
+ // 0 = use maxRcvBuffer (historical INET behavior)
+ uint32_t rcvBufferSize = 0;
+ // Linux SOCK_RCVBUF_LOCK: the application pinned the receive buffer with
+ // SO_RCVBUF, so the kernel may no longer grow it under pressure
+ // (tcp_clamp_window skips a locked buffer). Without a way to grow, a socket
+ // already over its budget has no choice but to drop what arrives next.
+ bool rcvbufLocked = false;
+ // Linux ICSK_ACK_NOMEM: the last arrival was dropped for want of buffer space,
+ // so the next ACK must advertise a zero window whatever the ordinary window
+ // computation says. One-shot -- sending that ACK clears it, as clearing the
+ // delayed-ACK timer does in the reference (tcp_event_ack_sent).
+ bool ackNomem = false;
+ // receiver window auto-tuning (Linux tcp_grow_window / tp->rcv_ssthresh):
+ // the OFFERED window starts at advertisedWindow and grows with received
+ // data toward window_clamp (derived from rcvBufferSize). 0 = disabled.
+ uint32_t rcv_ssthresh = 0;
+ uint32_t window_clamp = 0;
+ bool sawAccEcnOpt = false;
+ // Linux tp->accecn_minlen: minimum number of AccECN option fields the next
+ // option must carry to cover the counter(s) that changed since the last
+ // one was sent (order-1 field indices: ECT1=1, CE=2, ECT0=3; 0 = nothing
+ // pending). Consumed and reset by the option emission in
+ // writeHeaderOptions; also caps the SACK block count in addSacks so a
+ // REQUIRED option gets its space (tcp_options_fit_accecn's
+ // num_sack_blocks-- arm, down to 2 blocks at most).
+ uint8_t accEcnOptMinFields = 0;
+ // Linux tp->accecn_opt_sent_w_dsack + TCP_ACCECN_OPT_FAIL_SEND
+ // (tcp_rcv_spurious_retrans): when the previous ACK carried both the
+ // AccECN option and a D-SACK, and the peer nevertheless retransmits the
+ // very same duplicate again, our option-bearing ACKs are evidently being
+ // dropped by a middlebox -- stop sending the AccECN option for good
+ // (default sysctl_tcp_ecn_option = FULL, which yields to the fail mode).
+ // Linux tp->snd_sml (Minshall's Nagle variant): right edge of the most
+ // recently sent sub-MSS data segment. Nagle holds a trailing partial only
+ // while such a small segment is still unacknowledged -- full-MSS segments
+ // in flight alone never delay it (tcp_minshall_check).
+ uint32_t snd_sml = 0;
+ bool accEcnOptSentWithDsack = false;
+ uint32_t accEcnSentDsackStart = 0;
+ bool accEcnOptFailSend = false;
+ // Linux FLAG_TS_PROGRESS, per-segment (reset in readHeaderOptions like the
+ // option-delta valid flags): this segment's TS option advanced ts_recent by a
+ // positive delta. An ACK that acks no new data but carries a fresh timestamp
+ // still lets the ACE decode run (__tcp_accecn_process's forward-progress test).
+ bool accEcnTsProgress = false;
+
+ uint32_t dupthresh = 0; // used for TcpTahoe, TcpReno and SACK (RFC 6675)
// Path MTU Discovery (RFC 1191, RFC 1981)
bool pmtudEnabled = false; // set if Path MTU Discovery is enabled
uint32_t pmtudOriginalMss = 0; // negotiated MSS before any PMTUD reduction
simtime_t pmtudTimeout; // time after which the original MSS is restored to probe for increased path MTU
simtime_t pmtudLastMssReduction; // timestamp of last PMTUD-triggered MSS reduction (-1 = never reduced)
+
+ // Packetized Path MTU Discovery (RFC 4821, Linux icsk_mtup): the search runs in
+ // MTU units, so every bound below is an MTU, not an MSS. It probes UPWARD by
+ // sending one oversized segment built from data that is about to go out anyway
+ // -- if that segment is acknowledged the path carries it, and the lower bound
+ // moves up. Orthogonal to the RFC 1191 pmtud* fields above, which only ever
+ // shrink the MSS in response to an ICMP report.
+ bool mtupEnabled = false; // from the mtuProbing parameter (Linux tcp_mtu_probing > 1)
+ uint32_t mtupSearchHigh = 0; // largest MTU still worth trying (starts at the peer's MSS plus headers)
+ uint32_t mtupSearchLow = 0; // largest MTU known to work (starts at baseMss plus headers)
+ uint32_t mtupProbeSize = 0; // MTU of the probe currently in flight; 0 = not probing
+ uint32_t mtupProbeSeqStart = 0; // sequence range covered by that probe, so its ACK can be recognized
+ uint32_t mtupProbeSeqEnd = 0;
+ uint32_t mtupProbeBytes = 0; // payload the next sendSegment() call must build, bypassing the snd_mss clamp
+ uint32_t pathMtu = 0; // Linux icsk_pmtu_cookie: the route's MTU, the ceiling the search works up to
+ uint32_t initialCwndSegments = 0; // route initcwnd: initial congestion window in segments; 0 = derive from init_cwnd_mode
+
+ // TCP Fast Open (RFC 7413)
+ bool fastopenClientEnabled = false; // from the fastopenClientEnabled parameter
+ bool fastopenServerEnabled = false; // from the fastopenServerEnabled parameter
+ bool fastopenAcceptWithoutCookie = false; // server: accept SYN data carrying no cookie option at all (tcp_fastopen 0x200 / TFO_SERVER_COOKIE_NOT_REQD)
+ bool fastopenLenientCookieValidation = true; // from the fastopenLenientCookieValidation parameter
+ bool fastopenExpOptionEnabled = false; // from the fastopenExpOptionEnabled parameter
+ int fastopenCookieBytes = 8; // from the fastopenCookieBytes parameter
+ bool fastopenCookieRequested = false; // server: peer sent an empty-cookie TFO option this SYN
+ bool fastopenCookieValid = false; // server: peer's cookie validated (accelerate this SYN)
+ bool fastopenSendCookieOption = false; // server: echo a (possibly fresh) cookie in the SYN-ACK
+ bool fastopenPeerUsedExpOption = false; // server: the SYN's cookie/request came in the RFC 7413 Appendix A experimental form (kind 254 + 0xF989); echo in the SAME form, as Linux does
+ FastOpenCookie fastopenCookieToSend; // server: the cookie bytes to echo, if fastopenSendCookieOption
+ bool fastopenSynDeferred = false; // client: connect(fastOpen=true) is waiting for the app's first SEND (or the connEstabTimer safety net) before sending the SYN
+ bool fastopenCookieRequestPending = false; // client: no cookie was cached, so the (immediate, dataless) SYN requests one
+ uint32_t fastopenSynDataLen = 0; // client: bytes of app data attached to the SYN (0 = none); fixed once set, reused verbatim across SYN-REXMIT
+ bool fastopenRequested = false; // client: this connection's own connect() call opted into Fast Open (per-connection, like Linux's MSG_FASTOPEN/TCP_FASTOPEN_CONNECT) -- gates the cookie append independently of the module-wide fastopenClientEnabled parameter, so a plain connect() to a destination with a cached cookie does NOT send it uninvited
+ uint32_t peerAdvertisedMss = 0; // the RAW MSS value from the peer's SYN/SYN-ACK option, before any local (user-MSS/TCP_MAXSEG) clamping -- Linux caches THIS in tcp_metrics for the next TFO SYN's payload budget (tcp_rcv_fastopen_synack even reparses the SYN-ACK to bypass the user clamp; syn-data-mss pins the 1300-byte SYN payload from a cached 1340)
+ bool fastopenSynCarriedOption = false; // client: our SYN carried a TFO option (cookie or empty request) -- Linux tp->syn_fastopen; a SYN-ACK cookie is cached ONLY then (tcp_rcv_fastopen_synack ignores unsolicited cookies, cookie-less-sendto pins the next connect still REQUESTING)
+ bool fastopenSynDataAccepted = false; // server: a validated-cookie SYN carried data that was accepted and delivered ahead of the 3WHS; client: the SYN's data was acked by the SYN-ACK (both mirror Linux's TCPI_OPT_SYN_DATA tcp_info flag / tp->syn_data_acked)
+ bool fastopenAccelerated = false; // server: this connection was created from a TFO-accepted SYN (valid cookie or cookie-less mode), REGARDLESS of whether it carried data -- a zero-payload TFO SYN still entitles the app to respond from SYN_RCVD (Linux: the TFO child socket exists either way). Gates the SYN_RCVD send path; fastopenSynDataAccepted above stays data-only (it drives TCPI_OPT_SYN_DATA)
+ uint32_t pktsAckedEwma = 0; // AccECN: EWMA of packets acked per ACK (<<6 fixed point), for the ACE-wrap-vs-ACK-compression heuristic on large ACKs
};
cplusplus(TcpStateVariables) {{
diff --git a/src/inet/transportlayer/tcp/TcpConnectionUtil.cc b/src/inet/transportlayer/tcp/TcpConnectionUtil.cc
index 17e091ab98d..c7f07b2570b 100644
--- a/src/inet/transportlayer/tcp/TcpConnectionUtil.cc
+++ b/src/inet/transportlayer/tcp/TcpConnectionUtil.cc
@@ -12,8 +12,8 @@
#include // min,max
#include "inet/common/INETUtils.h"
-#include "inet/common/ProtocolTag_m.h"
#include "inet/common/packet/Message.h"
+#include "inet/common/ProtocolTag_m.h"
#include "inet/common/socket/SocketTag_m.h"
#include "inet/networklayer/common/DscpTag_m.h"
#include "inet/networklayer/common/IcmpType_m.h"
@@ -29,13 +29,19 @@
#include "inet/networklayer/contract/IL3AddressType.h"
#include "inet/transportlayer/common/L4Tools.h"
#include "inet/transportlayer/contract/tcp/TcpCommand_m.h"
+#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
+#include "inet/transportlayer/contract/tcp/TcpSendEorTag_m.h"
+#include "inet/transportlayer/contract/tcp/TcpSendMoreTag_m.h"
+#include "inet/transportlayer/contract/tcp/TcpTimestampingTag_m.h"
+#include "inet/transportlayer/contract/tcp/TcpZerocopyTag_m.h"
#include "inet/transportlayer/tcp/Tcp.h"
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
#include "inet/transportlayer/tcp/TcpConnection.h"
#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
#include "inet/transportlayer/tcp/TcpSendQueue.h"
-#include "inet/transportlayer/tcp_common/TcpHeader.h"
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
namespace inet {
namespace tcp {
@@ -236,6 +242,7 @@ void TcpConnection::initClonedConnection(TcpConnection *listenerConn)
// put it into LISTEN, with our localAddr/localPort
state->active = false;
state->fork = true;
+ state->forked = true; // durable: this conn must CLOSE on RST/hard-ICMP in SYN_RCVD, never re-listen (see the field's comment)
localAddr = listenerConn->localAddr;
localPort = listenerConn->localPort;
autoRead = listenerConn->autoRead;
@@ -255,6 +262,22 @@ TcpConnection *TcpConnection::cloneListeningConnection()
return conn;
}
+int TcpConnection::receivedEcnCodepoint(Packet *tcpSegment)
+{
+ auto ecnTag = tcpSegment->findTag();
+ return ecnTag ? ecnTag->getExplicitCongestionNotification() : IP_ECN_NOT_ECT;
+}
+
+uint8_t TcpConnection::accEcnReflectedAce(int ipEcnCodepoint)
+{
+ switch (ipEcnCodepoint) {
+ case IP_ECN_ECT_1: return 3; // 0b011
+ case IP_ECN_ECT_0: return 4; // 0b100
+ case IP_ECN_CE: return 6; // 0b110
+ default: return 2; // 0b010, Not-ECT (and anything unrecognized)
+ }
+}
+
void TcpConnection::sendToIP(Packet *tcpSegment, const Ptr& tcpHeader)
{
sentSegments++;
@@ -262,7 +285,7 @@ void TcpConnection::sendToIP(Packet *tcpSegment, const Ptr& tcpHeader
// record seq (only if we do send data) and ackno
if (tcpSegment->getByteLength() > tcpHeader->getChunkLength().get())
- emit(sndNxtSignal, tcpHeader->getSequenceNo());
+ emit(sndSeqSignal, tcpHeader->getSequenceNo());
emit(sndAckSignal, tcpHeader->getAckNo());
@@ -298,23 +321,102 @@ void TcpConnection::sendToIP(Packet *tcpSegment, const Ptr& tcpHeader
addresses->setSrcAddress(localAddr);
addresses->setDestAddress(remoteAddr);
+ // AccECN: the ACE field rides the same 3 flag bits AccECN's 3WHS
+ // negotiation used (aeBit,cwrBit,eceBit), re-purposed post-handshake as a mod-8 counter
+ // of CE-marked packets received. Every ACK-bearing segment after the handshake carries
+ // the current count; the SYN-ACK itself is excluded -- its accept codepoint is set
+ // during the handshake and must not be overridden here.
+ // The other exclusion is the ACK that closes the connection: once both FINs are
+ // done with (ours acknowledged, the peer's just received), Linux has handed the
+ // socket over to a tcp_timewait_sock, and the ACKs it emits -- starting with this
+ // very one -- are built by tcp_v4_send_ack, which knows nothing about AccECN and
+ // leaves all three bits clear (accecn close_local_close_then_remote_fin pins the
+ // bare "> . 1002:1002(0) ack 2" ending an otherwise ACE=5 connection).
+ // The 2MSL timer is armed by the FIN processing that decides we are going to
+ // TIME_WAIT, and stays armed for as long as we are there -- so "2MSL running"
+ // is exactly "this ACK belongs to the time-wait socket", including the very
+ // first one, which is still emitted while the FSM sits in FIN_WAIT_1/2.
+ bool timeWaitAck = the2MSLTimer != nullptr && the2MSLTimer->isScheduled();
+ if (state->accEcnNegotiated && tcpHeader->getAckBit() && !tcpHeader->getSynBit()
+ && !timeWaitAck)
+ {
+ // ...except for the ONE handshake-completing ACK, which instead reflects the
+ // IP-ECN codepoint the SYN-ACK arrived with (RFC 9768 section 3.2.3.2). It is
+ // what tells the server whether the network cleared or CE-marked the ECN field
+ // of the SYN-ACK it sent, and it cannot be a counter value: the count is
+ // necessarily still zero at that point, so the two uses of the field would be
+ // indistinguishable. Consumed here so the next ACK is a counter again.
+ uint8_t ace = state->accEcnReflectAce
+ ? accEcnReflectedAce(state->accEcnReflectCodepoint)
+ : (uint8_t)((state->rcvCePkts + 5) & 0x7);
+ state->accEcnReflectAce = false;
+ tcpHeader->setAeBit((ace >> 2) & 0x1);
+ tcpHeader->setCwrBit((ace >> 1) & 0x1);
+ tcpHeader->setEceBit(ace & 0x1);
+ }
+
+ // AccECN TCP option beacon bookkeeping: the exactly-once-per-
+ // real-send mutation companion to writeHeaderOptions()'s pure/idempotent beacon
+ // decision (which may run more than once per real segment as a header-size dry
+ // run -- see the comment there). This guard is deliberately broader than
+ // writeHeaderOptions()'s own (which only appends from its non-SYN/non-INIT/
+ // non-LISTEN else-if branch, so e.g. sendFin() -- which never calls
+ // writeHeaderOptions() at all -- and ACKs sent from LAST_ACK/CLOSING/TIME_WAIT
+ // never carry the option): counting here is a superset of appending, so a FIN
+ // or teardown ACK can advance accEcnAckCount/toggle the kind without the option
+ // ever actually going out. That's harmless -- neither the cadence nor which of
+ // 172/174 is used has a wire-correctness requirement (the peer decodes either
+ // kind identically) -- so "beacon every accEcnOptionBeaconAcks-th ACK-bearing
+ // segment" is closer to "at most every Nth" in practice; it is never a
+ // duplicate append, only an occasional silent skip/phase advance.
+ if (state->accEcnNegotiated && state->accEcnOptionEnabled && tcpHeader->getAckBit() && !tcpHeader->getSynBit()) {
+ state->accEcnAckCount++;
+ if (state->accEcnOptionBeaconAcks > 0 && state->accEcnAckCount % state->accEcnOptionBeaconAcks == 0)
+ if (state->accEcnOptionKindAlternates)
+ state->accEcnOptionNextKindIsAccEcn1 = !state->accEcnOptionNextKindIsAccEcn1;
+ }
+
// ECN:
- // We decided to use ECT(1) to indicate ECN capable transport.
+ // We decided to use ECT(0) to indicate ECN capable transport.
//
- // rfc-3168, page 6:
- // Routers treat the ECT(0) and ECT(1) codepoints
+ // RFC 3168, page 6
+ // "Routers treat the ECT(0) and ECT(1) codepoints
// as equivalent. Senders are free to use either the ECT(0) or the
- // ECT(1) codepoint to indicate ECT.
+ // ECT(1) codepoint to indicate ECT."
//
- // rfc-3168, page 20:
- // For the current generation of TCP congestion control algorithms, pure
+ // RFC 3168, page 20
+ // "For the current generation of TCP congestion control algorithms, pure
// acknowledgement packets (e.g., packets that do not contain any
- // accompanying data) MUST be sent with the not-ECT codepoint.
+ // accompanying data) MUST be sent with the not-ECT codepoint."
+ //
+ // RFC 3168, page 20
+ // "ECN-capable TCP implementations MUST NOT set either ECT codepoint
+ // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets"
+ // The one ECT decision for this segment is made below, and it is the only
+ // one: ecnMarkAll's mark-everything semantics are folded into it.
+ // RFC 3168 section 6.1.1: a host MUST NOT set an ECT codepoint on a SYN or
+ // SYN-ACK -- those are control segments and are always sent Not-ECT (this also
+ // matches Linux, whose AccECN/ECN SYN-ACK carries Not-ECT in the IP header
+ // even though state->ect is already true by then).
//
- // rfc-3168, page 20:
- // ECN-capable TCP implementations MUST NOT set either ECT codepoint
- // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets
- tcpSegment->addTagIfAbsent()->setExplicitCongestionNotification((state->ect && !state->sndAck && !state->rexmit) ? IP_ECN_ECT_1 : IP_ECN_NOT_ECT);
+ // AccECN (draft-ietf-tcpm-accurate-ecn section 3.1.5) reverses two of RFC
+ // 3168's restrictions: once AccECN is negotiated, the Data Sender sets ECT on
+ // EVERY packet except the SYN/SYN-ACK -- including pure ACKs, retransmissions
+ // and window probes -- so that congestion can be measured on the whole flow,
+ // not just new-data packets (Linux marks ECT(0) identically). Classic RFC 3168
+ // keeps the pure-ACK and retransmission exclusions.
+ bool markEct;
+ if (state->accEcnNegotiated)
+ markEct = state->ect && !tcpHeader->getSynBit();
+ else if (state->ecnMarkAll)
+ markEct = state->ect; // legacy testing knob: every packet, SYNs included
+ else
+ // classic RFC 3168 / Linux tcp_ecn_send(): ECT only on DATA-bearing,
+ // non-retransmitted, non-SYN segments -- a data-less FIN or pure ACK
+ // goes Not-ECT (the sndAck flag missed data-less FINs).
+ markEct = state->ect && tcpSegment->getByteLength() > 0
+ && !state->rexmit && !tcpHeader->getSynBit();
+ tcpSegment->addTagIfAbsent()->setExplicitCongestionNotification(markEct ? IP_ECN_ECT_0 : IP_ECN_NOT_ECT);
tcpHeader->setChecksum(0);
tcpHeader->setChecksumMode(tcpMain->checksumMode);
@@ -379,22 +481,76 @@ bool TcpConnection::processIcmpv4Error(Indication *indication)
<< " > " << remoteAddr << ":" << remotePort
<< " type=" << errorInd->getType() << " code=" << errorInd->getCode() << "\n";
- // Hard errors abort the connection during setup (RFC 5461).
+ // RFC 5927 / Linux tcp_v4_err(): validate the QUOTED sequence number
+ // against the send window before acting -- an ICMP error quoting a
+ // sequence outside [SND.UNA, SND.MAX] is stale or forged and must be
+ // ignored entirely (the icmp-before-accept scripts inject exactly such an
+ // out-of-window error first and expect it to have no effect).
+ if (const Packet *originalPacket = errorInd->getOriginalPacket()) {
+ const auto& quotedTcp = originalPacket->peekAtFront(b(-1), Chunk::PF_ALLOW_INCOMPLETE);
+ uint32_t quotedSeq = quotedTcp->getSequenceNo();
+ if (!seqLE(state->snd_una, quotedSeq) || !seqLE(quotedSeq, state->snd_max)) {
+ EV_DETAIL << "Ignoring ICMPv4 error quoting out-of-window sequence " << quotedSeq
+ << " (SND.UNA=" << state->snd_una << ", SND.MAX=" << state->snd_max << ")\n";
+ delete indication;
+ return true;
+ }
+ }
+
+ // Hard errors abort the connection during setup (RFC 5461) -- in SYN_SENT
+ // and in SYN_RCVD alike: Linux tcp_v4_err()/tcp_done_with_error() kills the
+ // pending request/child socket regardless of which side of the handshake it
+ // is on (a plain non-forked passive open falls back to LISTEN via the RCV_RST
+ // transition's gate, "request dropped, listener remains"; a forked or
+ // TFO-accelerated connection dies, see TcpConnectionBase's RCV_RST rule).
// Once ESTABLISHED, even hard errors are treated as soft to prevent
// blind reset attacks.
- if (isHardIcmpv4Error(errorInd->getType(), errorInd->getCode()) && fsm.getState() == TCP_S_SYN_SENT) {
+ if (isHardIcmpv4Error(errorInd->getType(), errorInd->getCode())
+ && (fsm.getState() == TCP_S_SYN_SENT || fsm.getState() == TCP_S_SYN_RCVD))
+ {
EV_DETAIL << "Hard ICMPv4 error during connection setup -- connection refused\n";
sendQueue->discardUpTo(sendQueue->getBufferEndSeq());
if (state->sack_enabled)
rexmitQueue->discardUpTo(rexmitQueue->getBufferEndSeq());
- sendIndicationToApp(TCP_I_CONNECTION_REFUSED);
+ // Only signal an app that actually owns this socket (an active opener,
+ // or a forked/TFO child); a plain passive listener just drops the
+ // half-open request and keeps listening, the user need not be informed.
+ if (state->active || state->forked || state->fastopenAccelerated)
+ sendIndicationToApp(TCP_I_CONNECTION_REFUSED);
delete indication;
return performStateTransition(TCP_E_RCV_RST);
}
+ // Linux reacts to an ICMP frag-needed in SYN_SENT immediately and
+ // unconditionally (tcp_v4_err -> tcp_simple_retransmit; kernel commit
+ // c31b70c9968f) -- this is NOT gated behind PMTUD proper, which governs
+ // the established-connection MSS-reduction path below: the SYN is
+ // retransmitted at once at the reduced MSS, with any Fast Open payload
+ // and option dropped from it (the bare-rexmit rule), and the SYN's own
+ // MSS option re-advertises the reduced value.
+ if (fsm.getState() == TCP_S_SYN_SENT && isFragNeeded(errorInd->getType(), errorInd->getCode())) {
+ int mtu = errorInd->getMtu();
+ uint32_t newMss = mtu > 40 ? mtu - 40 : 0; // 20B IPv4 header + 20B minimum TCP header
+ if (newMss > 0 && newMss < state->snd_mss) {
+ EV_DETAIL << "ICMP frag-needed in SYN_SENT: reducing MSS from " << state->snd_mss
+ << " to " << newMss << " and retransmitting a bare SYN immediately\n";
+ state->snd_mss = newMss;
+ if (newMss < state->advertisedMss)
+ state->advertisedMss = newMss;
+ // count as a retransmission so sendSyn()/writeHeaderOptions()'s
+ // bare-SYN rule (no FO option, no SYN data) applies, same as
+ // Linux's retransmit path
+ state->syn_rexmit_count++;
+ sendSyn();
+ rescheduleAfter(state->syn_rexmit_timeout, synRexmitTimer);
+ }
+ delete indication;
+ return true;
+ }
+
// PMTUD (RFC 1191): if Fragmentation Needed and DF Set, reduce snd_mss
if (state->pmtudEnabled && isFragNeeded(errorInd->getType(), errorInd->getCode())) {
int mtu = errorInd->getMtu();
@@ -446,14 +602,30 @@ bool TcpConnection::processIcmpv6Error(Indication *indication)
// Hard errors abort the connection during setup (RFC 5461).
// Once ESTABLISHED, even hard errors are treated as soft to prevent
// blind reset attacks.
- if (isHardIcmpv6Error(errorInd->getType(), errorInd->getCode()) && fsm.getState() == TCP_S_SYN_SENT) {
+ // same quoted-sequence validation as the IPv4 sibling
+ if (const Packet *originalPacket = errorInd->getOriginalPacket()) {
+ const auto& quotedTcp = originalPacket->peekAtFront(b(-1), Chunk::PF_ALLOW_INCOMPLETE);
+ uint32_t quotedSeq = quotedTcp->getSequenceNo();
+ if (!seqLE(state->snd_una, quotedSeq) || !seqLE(quotedSeq, state->snd_max)) {
+ EV_DETAIL << "Ignoring ICMPv6 error quoting out-of-window sequence " << quotedSeq << "\n";
+ delete indication;
+ return true;
+ }
+ }
+
+ if (isHardIcmpv6Error(errorInd->getType(), errorInd->getCode())
+ && (fsm.getState() == TCP_S_SYN_SENT || fsm.getState() == TCP_S_SYN_RCVD))
+ {
+ // same SYN_RCVD extension as the IPv4 sibling: the pending request /
+ // TFO child dies (or a plain passive open falls back to LISTEN)
EV_DETAIL << "Hard ICMPv6 error during connection setup -- connection refused\n";
sendQueue->discardUpTo(sendQueue->getBufferEndSeq());
if (state->sack_enabled)
rexmitQueue->discardUpTo(rexmitQueue->getBufferEndSeq());
- sendIndicationToApp(TCP_I_CONNECTION_REFUSED);
+ if (state->active || state->forked || state->fastopenAccelerated)
+ sendIndicationToApp(TCP_I_CONNECTION_REFUSED);
delete indication;
return performStateTransition(TCP_E_RCV_RST);
@@ -498,9 +670,15 @@ bool TcpConnection::processIcmpv6Error(Indication *indication)
bool TcpConnection::isHardIcmpv4Error(int type, int code)
{
- // ICMPv4 Destination Unreachable with protocol/port unreachable or admin prohibited
+ // Only consulted during connection setup (SYN_SENT/SYN_RCVD), where Linux
+ // tcp_v4_err() aborts on ANY Destination Unreachable code (for the SYN
+ // states the icmp_err_convert fatal flag is bypassed) -- so net/host
+ // unreachable are hard here too, EXCEPT frag-needed (code 4), which
+ // triggers the immediate reduced-MSS SYN retransmit instead.
return type == ICMP_DESTINATION_UNREACHABLE
- && (code == ICMP_DU_PROTOCOL_UNREACHABLE
+ && (code == ICMP_DU_NETWORK_UNREACHABLE
+ || code == ICMP_DU_HOST_UNREACHABLE
+ || code == ICMP_DU_PROTOCOL_UNREACHABLE
|| code == ICMP_DU_PORT_UNREACHABLE
|| code == ICMP_DU_COMMUNICATION_PROHIBITED);
}
@@ -563,19 +741,51 @@ void TcpConnection::sendToApp(cMessage *msg)
tcpMain->sendToApp(msg);
}
+void TcpConnection::updateSndbufLimitedChrono()
+{
+ // Linux TCP_CHRONO_SNDBUF_LIMITED (tcp_write_xmit's tail): the chrono runs
+ // while the transmission is STARVED by the send buffer -- the write queue
+ // has no unsent data (everything the buffer could hold is in flight) and
+ // the application writer is still blocked waiting for space (SOCK_NOSPACE,
+ // conveyed by TcpSetWriterBlockedCommand). Sampled at every event that can
+ // change the condition; tcp-info-sndbuf-limited pins the resulting ~20ms.
+ if (state == nullptr || sendQueue == nullptr)
+ return;
+ bool limited = writerBlocked
+ && sendQueue->getBytesAvailable(state->snd_nxt) == 0
+ && state->snd_una != state->snd_max;
+ if (limited && state->sndbufLimitedStartTime < SIMTIME_ZERO)
+ state->sndbufLimitedStartTime = simTime();
+ else if (!limited && state->sndbufLimitedStartTime >= SIMTIME_ZERO) {
+ state->sndbufLimitedAccumulated += simTime() - state->sndbufLimitedStartTime;
+ state->sndbufLimitedStartTime = -1;
+ }
+}
+
void TcpConnection::sendAvailableDataToApp()
{
if (receiveQueue->getAmountOfBufferedBytes()) {
if (autoRead || maxByteCountRequested > 0) {
uint32_t endSeqNo = state->rcv_nxt;
+ // rcv_nxt may already be advanced past a received FIN (which
+ // occupies a sequence number but has no bytes in the queue) --
+ // clamp extraction at the FIN or the queue's range assert trips
+ if (state->fin_rcvd && seqLess(state->rcv_fin_seq, endSeqNo))
+ endSeqNo = state->rcv_fin_seq;
if (!autoRead) {
uint32_t requestedEndPos = receiveQueue->getFirstSeqNo() + maxByteCountRequested;
if (seqLess(requestedEndPos, endSeqNo))
endSeqNo = requestedEndPos;
}
while (auto msg = receiveQueue->extractBytesUpTo(endSeqNo)) {
+ // windowShrinkAllowed accounting: reading releases receive-
+ // buffer occupancy skb by skb -- Linux frees an skb (and its
+ // whole truesize) only once it is fully copied to the user.
+ releaseRcvBufOccupancy(msg->getByteLength());
msg->setKind(TCP_I_DATA); // TBD currently we never send TCP_I_URGENT_DATA
msg->addTag()->setSocketId(socketId);
+ if (rxTimestampingEnabled)
+ msg->addTag();
sendToApp(msg);
if (!autoRead) {
maxByteCountRequested = 0;
@@ -636,44 +846,216 @@ void TcpConnection::configureStateVariables()
state->rcv_adv = advertisedWindow;
if (state->ws_support && advertisedWindow > TCP_MAX_WIN) {
- state->rcv_wnd = TCP_MAX_WIN; // we cannot to guarantee that the other end is also supporting the Window Scale (header option) (RFC 1322)
+ state->rcv_wnd = TCP_MAX_WIN; // we cannot to guarantee that the other end is also supporting the Window Scale (header option) (RFC 1122)
state->rcv_adv = TCP_MAX_WIN; // therefore TCP_MAX_WIN is used as initial value for rcv_wnd and rcv_adv
}
state->maxRcvBuffer = advertisedWindow;
+ // receive-buffer capacity decoupled from the advertised window (Linux
+ // sk_rcvbuf vs the offered window): out-of-order data may be buffered up
+ // to this limit; -1 keeps the historical conflation
+ {
+ int64_t rcvBufBytes = (int64_t)tcpMain->par("receiveBufferSize").doubleValue();
+ state->rcvBufferSize = rcvBufBytes >= 0 ? (uint32_t)rcvBufBytes : 0;
+ state->rcvbufLocked = tcpMain->par("receiveBufferLocked");
+ }
+ // An SO_RCVBUF that arrived before the connection was configured (the usual
+ // case: set between socket() and listen()).
+ if (rcvBufSockopt >= 0) {
+ state->rcvBufferSize = (uint32_t)rcvBufSockopt;
+ state->rcvbufLocked = true;
+ }
+ // receiver window auto-tuning (Linux tcp_grow_window): offer starts at
+ // advertisedWindow, grows with received data toward the clamp (Linux
+ // tcp_win_from_space halves the buffer at the default scaling ratio)
+ if (tcpMain->par("windowAutoTuning") && state->rcvBufferSize > 0) {
+ state->rcv_ssthresh = advertisedWindow;
+ state->window_clamp = std::max(advertisedWindow, state->rcvBufferSize / 2);
+ }
+ // windowShrinkAllowed: the whole window model follows the REAL buffer
+ // (Linux sk_rcvbuf = receiveBufferSize), not the advertisedWindow param --
+ // the initial offer is tcp_select_initial_window's win_from_space at the
+ // DEFAULT 50% scaling ratio, so the maximum promise the peer can hold us
+ // to (rcv_adv / Linux rcv_mwnd_seq) starts buffer-derived too
+ // (rcv_wnd_shrink_allowed's max promise comes solely from the first
+ // data-time offer).
+ if (tcpMain->par("windowShrinkAllowed").boolValue() && state->rcvBufferSize > 0) {
+ state->maxRcvBuffer = state->rcvBufferSize;
+ uint32_t initWin = std::min(state->rcvBufferSize / 2, TCP_MAX_WIN);
+ state->rcv_wnd = initWin;
+ state->rcv_adv = initWin;
+ if (tcpMain->par("windowAutoTuning") && state->rcvBufferSize > 0) {
+ state->rcv_ssthresh = std::max(state->rcv_ssthresh, state->rcvBufferSize);
+ state->window_clamp = std::max(state->window_clamp, state->rcvBufferSize);
+ }
+ }
+ state->rcv_mwnd_seq = state->rcv_adv;
state->delayed_acks_enabled = tcpMain->par("delayedAcksEnabled"); // delayed ACK algorithm (RFC 1122) enabled/disabled
- state->nagle_enabled = tcpMain->par("nagleEnabled"); // Nagle's algorithm (RFC 896) enabled/disabled
+ state->delayedAckFrameCount = tcpMain->par("delayedAckFrameCount");
+ state->nagle_enabled = tcpMain->par("nagleEnabled"); // Nagle's algorithm (RFC 1122) enabled/disabled
+ // A runtime TCP_NODELAY / TCP_CORK received before OPEN (nodelaySockopt/corkSockopt,
+ // INT_MIN = never set) overrides the compiled-in defaults (mirrors userMss/notsentLowat).
+ if (nodelaySockopt != INT_MIN)
+ state->nagle_enabled = (nodelaySockopt == 0);
+ if (corkSockopt != INT_MIN)
+ state->tcp_cork = (corkSockopt != 0);
+ state->adaptiveDelayedAcks = tcpMain->par("adaptiveDelayedAcks"); // Linux-shaped quickack/ATO/pingpong dynamics
+ state->pushOnWriteBoundary = tcpMain->par("pushSegmentsOnWriteBoundary"); // Linux-parity PSH-on-drain
state->limited_transmit_enabled = tcpMain->par("limitedTransmitEnabled"); // Limited Transmit algorithm (RFC 3042) enabled/disabled
state->increased_IW_enabled = tcpMain->par("increasedIWEnabled"); // Increased Initial Window (RFC 3390) enabled/disabled
- state->snd_mss = tcpMain->par("mss"); // Maximum Segment Size (RFC 793)
- state->ts_support = tcpMain->par("timestampSupport"); // if set, this means that current host supports TS (RFC 1323)
+ const char *initialWindow = tcpMain->par("initialWindow");
+ if (state->increased_IW_enabled) {
+ // deprecated knob: map to RFC 3390 unless initialWindow was also set away
+ // from its NED default
+ if (strcmp(initialWindow, "rfc6928") != 0)
+ throw cRuntimeError("Tcp: set either the deprecated increasedIWEnabled or initialWindow, not both");
+ EV_WARN << "Tcp: increasedIWEnabled is deprecated; use initialWindow=\"rfc3390\"\n";
+ state->init_cwnd_mode = 1;
+ }
+ else if (!strcmp(initialWindow, "rfc3390"))
+ state->init_cwnd_mode = 1;
+ else if (!strcmp(initialWindow, "rfc6928"))
+ state->init_cwnd_mode = 2;
+ else
+ state->init_cwnd_mode = 0;
+ {
+ int initialCwnd = tcpMain->par("initialCwnd");
+ if (initialCwnd < 0)
+ throw cRuntimeError("initialCwnd must be non-negative (0 = derive from initialWindow), but is %d", initialCwnd);
+ state->initialCwndSegments = (uint32_t)initialCwnd;
+ }
+ // Packetized PMTU discovery (RFC 4821). Only Linux's tcp_mtu_probing=2 has a
+ // counterpart here: 1 arms the search on black-hole detection, which needs a
+ // detector INET does not have.
+ state->mtupEnabled = (int)tcpMain->par("mtuProbing") > 1;
+ // Maximum Segment Size (RFC 9293). mss=-1 is a sentinel meaning "derive from the
+ // address family" -- resolved in writeHeaderOptions() once remoteAddr is known.
+ // Read as signed first: assigning -1 straight into the uint32_t snd_mss trips
+ // OMNeT++'s cPar overflow check.
+ int mssPar = tcpMain->par("mss");
+ if (mssPar == -1)
+ state->snd_mss = (uint32_t)-1;
+ else if (mssPar >= 64 && mssPar <= 65535)
+ state->snd_mss = (uint32_t)mssPar;
+ else
+ throw cRuntimeError("mss must be -1 (address-family default) or in the range 64..65535, but is %d", mssPar);
+ state->snd_effmss = calculateEffectiveMss();
+ state->ts_support = tcpMain->par("timestampSupport"); // if set, this means that current host supports TS (RFC 7323)
state->ecnWillingness = tcpMain->par("ecnWillingness"); // if set, current host is willing to use ECN
+ state->advertisedMss = state->snd_mss; // our own receive limit; stays unclamped when snd_mss later shrinks to the peer's MSS
+ // TCP_MAXSEG (setsockopt SOL_TCP, via TcpSetMaxSegCommand before OPEN): the app
+ // caps the MSS -- both what we advertise in our SYN/SYN-ACK and the effective
+ // send MSS (Linux rx_opt.user_mss). A later peer MSS option still clamps snd_mss
+ // further down (writeHeaderOptions/readHeaderOptions min), so a smaller peer MSS
+ // wins, but the peer can never raise us above userMss.
+ if (userMss > 0) {
+ state->advertisedMss = userMss;
+ if (state->snd_mss == (uint32_t)-1 || (uint32_t)userMss < state->snd_mss) {
+ state->snd_mss = userMss;
+ state->snd_effmss = calculateEffectiveMss();
+ }
+ }
+ // TCP_NOTSENT_LOWAT. -1 (default) disables it; same signed-read-
+ // first pattern as mss above, since -1 doesn't fit directly into the uint32_t field.
+ // A runtime TcpSetNotsentLowatCommand received before OPEN (notsentLowatSockopt,
+ // INT_MIN = never set) overrides the module parameter.
+ int notsentLowatPar = (notsentLowatSockopt != INT_MIN) ? notsentLowatSockopt : (int)tcpMain->par("notsentLowat");
+ state->notsentLowat = (notsentLowatPar < 0) ? (uint32_t)-1 : (uint32_t)notsentLowatPar;
+ // ECN mode resolution (AccECN): tcpEcnMode supersedes the deprecated
+ // ecnWillingness. Exact precedent: increasedIWEnabled vs initialWindow (above).
+ bool ecnWillingnessDeprecated = tcpMain->par("ecnWillingness");
+ const char *tcpEcnModeStr = tcpMain->par("tcpEcnMode");
+ if (ecnWillingnessDeprecated) {
+ if (strcmp(tcpEcnModeStr, "off") != 0)
+ throw cRuntimeError("Tcp: set either the deprecated ecnWillingness or tcpEcnMode, not both");
+ EV_WARN << "Tcp: ecnWillingness is deprecated; use tcpEcnMode=\"rfc3168\"\n";
+ state->ecnMode = TCP_ECN_MODE_RFC3168;
+ }
+ else if (!strcmp(tcpEcnModeStr, "passive"))
+ state->ecnMode = TCP_ECN_MODE_PASSIVE;
+ else if (!strcmp(tcpEcnModeStr, "rfc3168"))
+ state->ecnMode = TCP_ECN_MODE_RFC3168;
+ else if (!strcmp(tcpEcnModeStr, "accecn"))
+ state->ecnMode = TCP_ECN_MODE_ACCECN;
+ else if (!strcmp(tcpEcnModeStr, "accecn-passive"))
+ state->ecnMode = TCP_ECN_MODE_ACCECN_PASSIVE;
+ else
+ state->ecnMode = TCP_ECN_MODE_OFF;
+ // state->ecnWillingness reflects "willing to use ECN in some capacity" (accept and/or
+ // initiate) and is what the PASSIVE-open call sites (processSynInListen/sendSynAck) key
+ // off, unchanged since before AccECN -- true for every mode but "off". The asymmetry of
+ // "passive" and "accecn-passive" is about *initiating*, which the ACTIVE-open call site
+ // (sendSyn()) decides separately, directly from ecnMode, not from this flag. (This used
+ // to read `>= TCP_ECN_MODE_RFC3168`, which happens to exclude TCP_ECN_MODE_PASSIVE=1 --
+ // leaving Linux's own out-of-box default unable to accept the ECN-setup SYN it exists to
+ // accept.)
+ state->ecnWillingness = state->ecnMode != TCP_ECN_MODE_OFF;
+ state->accEcnOptionEnabled = tcpMain->par("accEcnOptionEnabled");
+ state->accEcnOptionBeaconAcks = tcpMain->par("accEcnOptionBeaconAcks");
+ state->accEcnOptionKindAlternates = tcpMain->par("accEcnOptionKindAlternates");
state->dupthresh = tcpMain->par("dupthresh");
- state->sack_support = tcpMain->par("sackSupport"); // if set, this means that current host supports SACK (RFC 2018, 2883, 3517)
+ state->frtoEnabled = tcpMain->par("frtoEnabled");
+ state->tlpEnabled = tcpMain->par("tlpEnabled");
+ state->seedRttFromHandshake = tcpMain->par("seedRttFromHandshake");
+ state->adaptiveReorderingEnabled = tcpMain->par("adaptiveReorderingEnabled");
+ state->maxReordering = tcpMain->par("maxReordering");
+ state->reordering = state->dupthresh; // dynamic DupThresh starts at the static value
+ state->lossUndoEnabled = tcpMain->par("lossUndoEnabled");
+ state->prrEnabled = tcpMain->par("prrEnabled");
+ state->lossDetectionMode = !strcmp(tcpMain->par("lossDetectionMode"), "rack") ? 1 : 0;
+ state->sack_support = tcpMain->par("sackSupport"); // if set, this means that current host supports SACK (RFC 2018, 2883, 6675)
+ // SACK-based (RFC 6675) loss recovery is provided by flavours whose createRecovery()
+ // can return an Rfc6675Recovery (TcpReno, TcpNewReno). Other flavours (TcpTahoe,
+ // TcpVegas, TcpWestwood, DumbTcp, ...) have no SACK recovery path. Rather than error
+ // -- which would make it impossible to turn sackSupport on by default -- treat
+ // sackSupport as a willingness (as Linux does; SACK is orthogonal to the congestion
+ // control) and simply do not use SACK for a flavour that cannot recover with it.
+ if (state->sack_support && !tcpAlgorithm->supportsSackRecovery()) {
+ EV_WARN << "sackSupport=true but tcpAlgorithmClass=\"" << tcpAlgorithm->getClassName()
+ << "\" has no SACK-based loss recovery; disabling SACK for this connection\n";
+ state->sack_support = false;
+ }
+ if (state->lossDetectionMode == 1 && !state->sack_support) {
+ // RACK needs the SACK scoreboard. Rather than make the connection
+ // unusable, fall back to classic DupThresh -- the same "willingness"
+ // treatment sackSupport itself gets just above, so that turning RACK on
+ // by default cannot break a flavour or peer that ends up without SACK.
+ EV_WARN << "lossDetectionMode=\"rack\" requires SACK, which is not enabled for this "
+ "connection; falling back to DupThresh loss detection\n";
+ state->lossDetectionMode = 0;
+ }
state->pmtudEnabled = tcpMain->par("pmtudEnabled"); // Path MTU Discovery (RFC 1191, RFC 1981)
state->pmtudTimeout = tcpMain->par("pmtudTimeout"); // time after which original MSS is restored
state->pmtudLastMssReduction = -1; // never reduced yet
+ state->dsack_enabled = tcpMain->par("dsackEnabled"); // if set, this means that current host supports SACK (RFC 2018, 2883, 6675)
+
+ // TCP_INFO trio: idle/not-limited until the first SEND/sendData() call says
+ // otherwise (enqueueSendCommandData()/sendData()).
+ state->busyStartTime = -1;
+ state->rwndLimitedStartTime = -1;
+ state->sndbufLimitedStartTime = -1;
+
+ state->fastopenClientEnabled = tcpMain->par("fastopenClientEnabled"); // TCP Fast Open (RFC 7413)
+ state->fastopenServerEnabled = tcpMain->par("fastopenServerEnabled");
+ state->fastopenAcceptWithoutCookie = tcpMain->par("fastopenAcceptWithoutCookie");
+ state->fastopenLenientCookieValidation = tcpMain->par("fastopenLenientCookieValidation");
+ state->fastopenExpOptionEnabled = tcpMain->par("fastopenExpOptionEnabled");
+ int fastopenCookieBytes = tcpMain->par("fastopenCookieBytes");
+ if (fastopenCookieBytes < 4 || fastopenCookieBytes > 16)
+ throw cRuntimeError("fastopenCookieBytes must be in the range 4..16 (RFC 7413 SS4), but is %d", fastopenCookieBytes);
+ state->fastopenCookieBytes = fastopenCookieBytes;
WATCH_EXPR("snd_nxt", state->snd_nxt);
WATCH_EXPR("rcv_nxt", state->rcv_nxt);
WATCH_EXPR("snd_una", state->snd_una);
- if (state->sack_support) {
- std::string algorithmName1 = "TcpReno";
- std::string algorithmName2 = tcpMain->par("tcpAlgorithmClass");
-
- if (algorithmName1 != algorithmName2) { // TODO add additional checks for new SACK supporting algorithms here once they are implemented
- EV_DEBUG << "If you want to use TCP SACK please set tcpAlgorithmClass to TcpReno\n";
-
- ASSERT(false);
- }
- }
}
void TcpConnection::selectInitialSeqNum()
{
// set the initial send sequence number
- state->iss = (unsigned long)fmod(SIMTIME_DBL(simTime()) * 250000.0, 1.0 + (double)(unsigned)0xffffffffUL) & 0xffffffffUL;
+ int64_t iss = tcpMain->par("initialSendSequenceNumber");
+ state->iss = iss != -1 ? iss : (unsigned long)fmod(SIMTIME_DBL(simTime()) * 250000.0, 1.0 + (double)(unsigned)0xffffffffUL) & 0xffffffffUL;
state->snd_una = state->snd_nxt = state->snd_max = state->iss;
@@ -684,7 +1066,7 @@ void TcpConnection::selectInitialSeqNum()
bool TcpConnection::isSegmentAcceptable(Packet *tcpSegment, const Ptr& tcpHeader) const
{
// check that segment entirely falls in receive window
- // RFC 793, page 69:
+ // RFC 9293, 3.10.7.4. Other States:
// "There are four cases for the acceptability test for an incoming segment:
// Segment Receive Test
// Length Window
@@ -701,21 +1083,56 @@ bool TcpConnection::isSegmentAcceptable(Packet *tcpSegment, const Ptrrcv_wnd == 0)
- ret = (seqNo == state->rcv_nxt);
- else // rcv_wnd > 0
-// ret = seqLE(state->rcv_nxt, seqNo) && seqLess(seqNo, rcvWndEnd);
- ret = seqLE(state->rcv_nxt, seqNo) && seqLE(seqNo, rcvWndEnd); // Accept an ACK on end of window
+ // Linux tcp_sequence() judges a segment carrying no data against the HIGHEST
+ // window edge ever promised (rcv_mwnd_seq, rcv_adv here), not against the
+ // current offer, and has no special case for a closed window. A peer that was
+ // once allowed to send that far may legitimately still be acknowledging from
+ // there -- which is exactly what it does after the window has been pulled back
+ // under it (rcv_wnd_shrink_nomem's pure ACK at the old right edge). The
+ // maximum only ever grows, so where no larger window was ever offered this
+ // stays the RFC 9293 test.
+ uint32_t promiseEnd = seqGreater(state->rcv_mwnd_seq, rcvWndEnd) ? state->rcv_mwnd_seq : rcvWndEnd;
+ ret = seqLE(state->rcv_nxt, seqNo) && seqLE(seqNo, promiseEnd); // Accept an ACK on end of window
}
else { // len > 0
if (state->rcv_wnd == 0)
ret = false;
- else // rcv_wnd > 0
+ else { // rcv_wnd > 0
+ // RFC 9293 SEG.LEN "counts SYN and FIN" (Linux end_seq): a segment that
+ // re-delivers already-received data but carries a FIN at RCV.NXT (a peer
+ // retransmitting its last data together with the FIN -- tcp_close_no_rst)
+ // has all its *data* below the window, yet its FIN sits at the window's
+ // left edge and the segment must be accepted so the FIN is processed.
+ // Include the SYN/FIN slot in the end-sequence test only (the branch
+ // selection above stays data-length based, so a pure FIN at a zero window
+ // still takes the len==0 path).
+ uint32_t endSeq = seqNo + len + tcpHeader->getSynFinLen();
ret = (seqLE(state->rcv_nxt, seqNo) && seqLess(seqNo, rcvWndEnd))
- || (seqLess(state->rcv_nxt, seqNo + len) && seqLE(seqNo + len, rcvWndEnd)); // Accept an ACK on end of window
+ || (seqLess(state->rcv_nxt, endSeq) && seqLE(endSeq, rcvWndEnd)); // Accept an ACK on end of window
+ // Linux BEYOND-WINDOW rule (SKB_DROP_REASON_TCP_INVALID_END_SEQUENCE
+ // / LINUX_MIB_TCPBEYONDWINDOW): a data segment whose end reaches
+ // beyond the HIGHEST window edge ever promised (rcv_mwnd_seq)
+ // is discarded whole -- classic trim-to-window would let a sender
+ // blast arbitrarily far past what was offered. Exception, same as
+ // the buffer-side over-accept: an in-order segment arriving to an
+ // EMPTY receive queue is taken (rcv_big_endseq pins the drop
+ // three times, then the accept-once-read case; rcv_zero_wnd_fin
+ // and rcv_neg_window pin the empty-queue acceptance).
+ // tcp_sequence() in the reference kernel: with end_seq past the
+ // promise, a segment whose START is also past it is always
+ // dropped; otherwise it is accepted ONLY when sk_receive_queue --
+ // the IN-ORDER unread queue, getAcknowledgedDataLength() here --
+ // is empty (out-of-order buffer content is irrelevant, and the
+ // arriving segment itself may be out of order).
+ if (ret && seqGreater(seqNo + len, state->rcv_mwnd_seq)) {
+ if (seqGreater(seqNo, state->rcv_mwnd_seq)
+ || receiveQueue->getAcknowledgedDataLength() != 0)
+ ret = false;
+ }
+ }
}
- // RFC 793, page 25:
+ // RFC 9293, 3.4. Sequence Numbers:
// "A new acknowledgment (called an "acceptable ack"), is one for which
// the inequality below holds:
// SND.UNA < SEG.ACK =< SND.NXT"
@@ -748,32 +1165,120 @@ void TcpConnection::sendSyn()
updateRcvWnd();
tcpHeader->setWindow(state->rcv_wnd);
- state->snd_max = state->snd_nxt = state->iss + 1;
+ // TCP Fast Open (RFC 7413): fastopenSynDataLen is 0 unless process_SEND's
+ // deferred-SYN path attached data; idempotent across SYN-REXMIT calls,
+ // same as the plain snd_max/snd_nxt assignment already was.
+ uint32_t synDataLen = state->fastopenSynDataLen;
+ state->snd_max = state->snd_nxt = state->iss + 1 + synDataLen;
+ emit(sndMaxSignal, state->snd_max);
+ state->full_sized_segment_counter = 0;
+
+ // Fast Open data rides on the SYN, bypassing the normal sendSegment() path that
+ // would otherwise register it in the rexmit queue. Register it here, or the queue's
+ // end stays at iss+1 while snd_una advances past it once the SYN-ACK arrives, and
+ // discardUpTo() -- which the connection now calls unconditionally, not only when
+ // SACK is enabled -- trips its range assertion.
+ if (synDataLen > 0 && rexmitQueue->getBufferEndSeq() == state->iss + 1) {
+ // register the SYN's payload in the SACK scoreboard: SACK is not yet
+ // negotiated when the data-bearing SYN goes out, but if the SYN-ACK
+ // enables it, the handshake ACK's discardUpTo must find the acked
+ // range in the queue. ONLY ONCE -- a SYN retransmit goes out WITHOUT
+ // the data (RFC 7413 fallback), so re-registering the range here
+ // would tag it retransmitted and keep it counted in flight, choking
+ // the post-SYN-rexmit one-segment window right when the fallback
+ // needs to send the data with the handshake ACK (cookie-less-sendto).
+ rexmitQueue->enqueueSentData(state->iss + 1, state->iss + 1 + synDataLen);
+ // Linux tcp_send_syn_data creates the SYN-payload skb with TCPHDR_PSH
+ // already set, so a post-fallback retransmit of that data carries PSH
+ // even mid-write (syn-data-only-syn-acked pins "P. 1:1421" on the
+ // full-MSS retransmit of a 6000-byte sendto's SYN portion).
+ if (state->pushOnWriteBoundary)
+ pushSeqNums.insert(state->iss + 1 + synDataLen);
+ }
- // ECN
- if (state->ecnWillingness) {
+ // ECN. Active-open initiation is decided directly from ecnMode, not from the shared
+ // ecnWillingness flag (which also covers the passive-accept side) -- accecn-passive
+ // is willing to ACCEPT AccECN when listening but must never INITIATE it (or classic ECN)
+ // on an active open, same as "passive"/"off".
+ if (state->ecnMode == TCP_ECN_MODE_ACCECN) {
+ // draft-ietf-tcpm-accurate-ecn 3WHS: the AccECN-requesting SYN sets ECE=CWR=AE=1
+ // (the "SEWA" codepoint, distinct from classic ECN's "SEW").
+ tcpHeader->setEceBit(true);
+ tcpHeader->setCwrBit(true);
+ tcpHeader->setAeBit(true);
+ state->aeSynSent = true;
+ state->ecnSynSent = false; // this is an AccECN attempt, not classic -- aeSynSent tracks it
+ EV << "AccECN-setup SYN packet sent\n";
+ }
+ else if (state->ecnMode == TCP_ECN_MODE_RFC3168) {
tcpHeader->setEceBit(true);
tcpHeader->setCwrBit(true);
state->ecnSynSent = true;
EV << "ECN-setup SYN packet sent\n";
}
else {
- // rfc 3168 page 16:
- // A host that is not willing to use ECN on a TCP connection SHOULD
+ // RFC 3168, page 16
+ // "A host that is not willing to use ECN on a TCP connection SHOULD
// clear both the ECE and CWR flags in all non-ECN-setup SYN and/or
- // SYN-ACK packets that it sends to indicate this unwillingness.
+ // SYN-ACK packets that it sends to indicate this unwillingness."
+ // Covers off, passive, and accecn-passive: none of these initiate on active OPEN.
tcpHeader->setEceBit(false);
tcpHeader->setCwrBit(false);
state->ecnSynSent = false;
// EV << "non-ECN-setup SYN packet sent\n";
}
+ // ECN blackhole fallback (Linux net.ipv4.tcp_ecn_fallback, on by default):
+ // a SYN that has already been retransmitted twice with its ECN bits set is
+ // most likely being dropped BECAUSE of them (middleboxes that choke on
+ // ECE/CWR/AE are the reason this fallback exists), so the third and later
+ // transmissions go out bare. Only the wire bits are cleared -- aeSynSent /
+ // ecnSynSent stay set, so a peer that does answer with an ECN or AccECN
+ // SYN-ACK is still understood and the negotiation completes normally
+ // (accecn syn_ace_flags_acked_after_retransmit pins exactly that: a bare
+ // 3rd SYN, an AccECN SYN-ACK, and a reflecting AccECN 3rd ACK).
+ if (state->syn_rexmit_count >= 2
+ && (tcpHeader->getEceBit() || tcpHeader->getCwrBit() || tcpHeader->getAeBit()))
+ {
+ EV << "SYN retransmitted " << state->syn_rexmit_count
+ << " times: falling back to a non-ECN-setup SYN\n";
+ tcpHeader->setEceBit(false);
+ tcpHeader->setCwrBit(false);
+ tcpHeader->setAeBit(false);
+ }
+
// write header options
writeHeaderOptions(tcpHeader);
- Packet *fp = new Packet("SYN");
+ // A retransmitted SYN goes out BARE: Linux drops both the Fast Open
+ // option and the SYN data on rexmit (tcp_retransmit_skb; RFC 7413
+ // section 4.1.3) -- the data stays registered (snd_max above is
+ // unchanged), so once the handshake completes with the data unacked it is
+ // retransmitted through the normal established path.
+ bool attachSynData = synDataLen > 0 && state->syn_rexmit_count == 0;
+ Packet *fp = attachSynData ? sendQueue->createSegmentWithBytes(state->iss + 1, synDataLen) : new Packet("SYN");
+
+ state->handshakeSentTime = simTime(); // for the handshake RTT seed on ESTABLISHED
// send it
sendToIP(fp, tcpHeader);
+
+ // TCP Fast Open SYN-data (MSG_ZEROCOPY): the payload rode out on the SYN via
+ // createSegmentWithBytes() above, NOT through sendSegment(), so its zerocopy
+ // completion would otherwise never fire. snd_nxt has already advanced past the
+ // SYN data (iss+1+synDataLen), so drain any pending completion now -- same rule
+ // as sendSegment()'s drain (see there).
+ while (synDataLen > 0 && !zerocopySeqNums.empty()
+ && !seqGreater(zerocopySeqNums.begin()->first, state->snd_nxt)) {
+ uint32_t zerocopyId = zerocopySeqNums.begin()->second;
+ zerocopySeqNums.erase(zerocopySeqNums.begin());
+ EV_INFO << "Notifying app: ZEROCOPY_COMPLETION id=" << zerocopyId << " (SYN-data)\n";
+ auto *completionIndication = new Indication("ZerocopyCompletion", TCP_I_ZEROCOPY_COMPLETION);
+ auto *completionInfo = new TcpZerocopyCompletionInfo();
+ completionInfo->setZerocopyId(zerocopyId);
+ completionIndication->addTag()->setSocketId(socketId);
+ completionIndication->setControlInfo(completionInfo);
+ sendToApp(completionIndication);
+ }
}
void TcpConnection::sendSynAck()
@@ -787,10 +1292,47 @@ void TcpConnection::sendSynAck()
updateRcvWnd();
tcpHeader->setWindow(state->rcv_wnd);
- state->snd_max = state->snd_nxt = state->iss + 1;
+ // Floor snd_nxt/snd_max at iss+1 (the SYN-ACK consumes iss) but NEVER roll
+ // them back: a SYN-ACK RETRANSMISSION for a TCP Fast Open server that already
+ // sent response data from SYN_RCVD must keep that data counted in snd_max, or
+ // the next data RTO retransmits zero bytes and asserts. The previous guard
+ // was dead code -- it overwrote snd_max with iss+1 first, so its own
+ // seqLess() checks compared against the just-written value and never fired.
+ if (seqLess(state->snd_nxt, state->iss + 1))
+ state->snd_nxt = state->iss + 1;
+ if (seqLess(state->snd_max, state->iss + 1))
+ state->snd_max = state->iss + 1;
+ emit(sndMaxSignal, state->snd_max);
// ECN
- if (state->ecnWillingness) {
+ if (state->accEcnNegotiated) {
+ // draft-ietf-tcpm-accurate-ecn 3WHS accept codepoint. The ACE field of the SYN-ACK
+ // is not a fixed value: it REFLECTS the IP-ECN codepoint the SYN arrived with
+ // (RFC 9768 section 3.2.3.2), which is how the client learns whether the network
+ // preserved, cleared or CE-marked the ECN field of its SYN. Not-ECT -- by far the
+ // common case, and what an unmarked SYN yields -- reflects as 0b010, i.e. exactly
+ // the "SW." accept codepoint this used to hardcode.
+ // The server marks ECT the same as the client (the active-open side already does
+ // this on accept) -- AccECN is still ECN-capable transport, and a server that never
+ // marks ECT can never actually observe a CE mark to report back via the ACE field.
+ // ect and accEcnNegotiated are NOT mutually exclusive: once negotiated, both are true
+ // together, and every classic-ECN read/write site that consumes eceBit/cwrBit for its
+ // own (ECE-echo / CWR) purposes must additionally check !accEcnNegotiated, since those
+ // same 3 bits are repurposed post-handshake as the ACE counter (see sendToIP()).
+ uint8_t ace = accEcnReflectedAce(state->accEcnReflectCodepoint);
+ tcpHeader->setAeBit((ace >> 2) & 0x1);
+ tcpHeader->setCwrBit((ace >> 1) & 0x1);
+ tcpHeader->setEceBit(ace & 0x1);
+ state->ect = true;
+ EV << "AccECN-setup SYN-ACK sent... AccECN is enabled (ACE=" << (int)ace << ", reflecting the SYN's IP-ECN)\n";
+ }
+ else if (state->ecnWillingness && state->endPointIsWillingECN) {
+ // Both halves of the condition matter: RFC 3168 section 6.1.1 makes the
+ // ECN-setup SYN-ACK an ANSWER to an ECN-setup SYN, so being willing is not
+ // on its own a licence to send one (Linux tcp_ecn_make_synack keys off the
+ // request sock's ecn_ok, which is only set when the SYN asked). Answering a
+ // plain SYN with ECE=1 claims a negotiation the client never opened --
+ // accecn notecn_then_accecn_syn pins the bare "> S." reply.
tcpHeader->setEceBit(true);
tcpHeader->setCwrBit(false);
EV << "ECN-setup SYN-ACK packet sent\n";
@@ -801,30 +1343,53 @@ void TcpConnection::sendSynAck()
if (state->endPointIsWillingECN)
EV << "non-ECN-setup SYN-ACK packet sent\n";
}
- if (state->ecnWillingness && state->endPointIsWillingECN) {
+ if (state->accEcnNegotiated) {
+ // ect already set to true above.
+ }
+ else if (state->ecnWillingness && state->endPointIsWillingECN) {
state->ect = true;
EV << "both end-points are willing to use ECN... ECN is enabled\n";
}
else { // TODO not sure if we have to.
- // rfc-3168, page 16:
- // A host that is not willing to use ECN on a TCP connection SHOULD
+ // RFC 3168, page 16
+ // "A host that is not willing to use ECN on a TCP connection SHOULD
// clear both the ECE and CWR flags in all non-ECN-setup SYN and/or
- // SYN-ACK packets that it sends to indicate this unwillingness.
+ // SYN-ACK packets that it sends to indicate this unwillingness."
state->ect = false;
if (state->endPointIsWillingECN)
EV << "ECN is disabled\n";
}
+ // ECN blackhole fallback, SYN-ACK side (the mirror of the sendSyn() case):
+ // twice retransmitted with the ECN bits set is taken as evidence that they
+ // are why it is not getting through, so the third and later SYN-ACKs go out
+ // bare. The negotiation state is deliberately left alone -- accecn
+ // multiple_syn_ack_drop pins a handshake that still completes on the bare
+ // SYN-ACK, and listen_opt_drop pins the retransmit ladder itself (option
+ // dropped on the 1st retransmit, ECN bits on the 2nd).
+ if (state->syn_rexmit_count >= 2
+ && (tcpHeader->getEceBit() || tcpHeader->getCwrBit() || tcpHeader->getAeBit()))
+ {
+ EV << "SYN-ACK retransmitted " << state->syn_rexmit_count
+ << " times: falling back to a non-ECN-setup SYN-ACK\n";
+ tcpHeader->setEceBit(false);
+ tcpHeader->setCwrBit(false);
+ tcpHeader->setAeBit(false);
+ }
+
// write header options
writeHeaderOptions(tcpHeader);
Packet *fp = new Packet("SYN+ACK");
+ state->handshakeSentTime = simTime(); // for the handshake RTT seed on ESTABLISHED
+
// send it
sendToIP(fp, tcpHeader);
// notify
tcpAlgorithm->ackSent();
+ state->full_sized_segment_counter = 0;
}
void TcpConnection::sendRst(uint32_t seqNo)
@@ -864,6 +1429,21 @@ void TcpConnection::sendRstAck(uint32_t seq, uint32_t ack, L3Address src, L3Addr
tcpHeader->setChecksumMode(tcpMain->checksumMode);
tcpHeader->setChecksum(0);
+ // A reset on a timestamp-negotiated connection carries the TS option like
+ // any other segment (Linux active resets go through the regular option
+ // builder; ts_recent/reset_tsval pins 'R. <...> TS val ecr ').
+ // state is null for a stateless reset reply -- no negotiated options there.
+ if (state != nullptr && state->ts_enabled) {
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ TcpOptionTimestamp *option = new TcpOptionTimestamp();
+ option->setSenderTimestamp(convertSimtimeToTS(simTime()));
+ option->setEchoedTimestamp(state->ts_recent);
+ tcpHeader->appendHeaderOption(option);
+ tcpHeader->setHeaderLength(TCP_MIN_HEADER_LENGTH + tcpHeader->getHeaderOptionArrayLength());
+ tcpHeader->setChunkLength(tcpHeader->getHeaderLength());
+ }
+
Packet *fp = new Packet("RST+ACK");
// send it
@@ -883,8 +1463,8 @@ void TcpConnection::sendAck()
tcpHeader->setAckNo(state->rcv_nxt);
tcpHeader->setWindow(updateRcvWnd());
- // rfc-3168, pages 19-20:
- // When TCP receives a CE data packet at the destination end-system, the
+ // RFC 3168, pages 19-20
+ // "When TCP receives a CE data packet at the destination end-system, the
// TCP data receiver sets the ECN-Echo flag in the TCP header of the
// subsequent ACK packet.
// ...
@@ -893,10 +1473,12 @@ void TcpConnection::sendAck()
// packets it sends (whether they acknowledge CE data packets or non-CE
// data packets) until it receives a CWR packet (a packet with the CWR
// flag set). After the receipt of the CWR packet, acknowledgments for
- // subsequent non-CE data packets do not have the ECN-Echo flag set.
+ // subsequent non-CE data packets do not have the ECN-Echo flag set."
TcpStateVariables *state = getStateForUpdate();
- if (state && state->ect) {
+ // AccECN connections repurpose eceBit as part of the post-handshake ACE counter
+ // (encoded later in sendToIP()); classic ECE-echo must not also write it here.
+ if (state && state->ect && !state->accEcnNegotiated) {
if (tcpAlgorithm->shouldMarkAck()) {
tcpHeader->setEceBit(true);
EV_INFO << "In ecnEcho state... send ACK with ECE bit set\n";
@@ -907,7 +1489,9 @@ void TcpConnection::sendAck()
writeHeaderOptions(tcpHeader);
Packet *fp = new Packet("TcpAck");
- // rfc-3168 page 20: pure ack packets must be sent with not-ECT codepoint
+ // RFC 3168, page 20
+ // "pure acknowledgement packets (e.g., packets that do not contain any
+ // accompanying data) MUST be sent with the not-ECT codepoint."
state->sndAck = true;
// send it
@@ -932,6 +1516,12 @@ void TcpConnection::sendFin()
tcpHeader->setWindow(updateRcvWnd());
Packet *fp = new Packet("FIN");
+ // RFC 7323: once Timestamps are negotiated, EVERY segment carries the TS
+ // option, a bare FIN included (sendAck()/sendSegment() already do this;
+ // without it the peer's PAWS/RTT bookkeeping never sees the FIN and a
+ // Linux peer's last ACK's TSecr goes backwards).
+ writeHeaderOptions(tcpHeader);
+
// send it
sendToIP(fp, tcpHeader);
@@ -965,11 +1555,52 @@ uint32_t TcpConnection::sendSegment(uint32_t bytes)
}
}
+ // RFC 4821: retransmitting into the range an outstanding probe covers is how
+ // this side learns the probe did not get through. Linux reaches the same
+ // conclusion in tcp_fastretrans_alert, from the loss estimator rather than from
+ // the retransmission itself; the trigger differs, the verdict does not.
+ if (state->mtupProbeSize != 0 && seqLess(state->snd_nxt, state->snd_max)
+ && seqGE(state->snd_nxt, state->mtupProbeSeqStart)
+ && seqLess(state->snd_nxt, state->mtupProbeSeqEnd))
+ mtupProbeFailed();
+
uint32_t buffered = sendQueue->getBytesAvailable(state->snd_nxt);
if (bytes > buffered) // last segment?
bytes = buffered;
+ // The post-RTO snd_nxt forwarding above can land exactly at the end of the
+ // send queue (everything from the old snd_nxt was SACKed/retransmitted);
+ // building a zero-byte segment would abort in createSegmentWithBytes()
+ // ("empty chunk"). Report "nothing sent" instead -- callers stop their
+ // send loops on a zero return.
+ if (bytes == 0)
+ return 0;
+
+ // MSG_EOR: boundaries at or behind snd_una are already fully
+ // acked and no longer relevant to anything sendSegment() might build from here
+ // on; drop them so the set doesn't grow across a long connection's lifetime.
+ while (!eorSeqNums.empty() && !seqGreater(*eorSeqNums.begin(), state->snd_una))
+ eorSeqNums.erase(eorSeqNums.begin());
+ while (!pushSeqNums.empty() && !seqGreater(*pushSeqNums.begin(), state->snd_una))
+ pushSeqNums.erase(pushSeqNums.begin());
+ while (!forcedPushSeqNums.empty() && !seqGreater(*forcedPushSeqNums.begin(), state->snd_una))
+ forcedPushSeqNums.erase(forcedPushSeqNums.begin());
+
+ // A record boundary must never be spanned by one segment: clamp bytes so this
+ // segment ends exactly at the nearest boundary ahead of snd_nxt, if closer than
+ // what was requested. Applies equally to fresh sends and retransmissions, since
+ // both funnel through here and the boundary is keyed on sequence number, not on
+ // send-queue position.
+ if (!eorSeqNums.empty()) {
+ auto it = eorSeqNums.upper_bound(state->snd_nxt);
+ if (it != eorSeqNums.end()) {
+ uint32_t distanceToBoundary = *it - state->snd_nxt;
+ if (bytes > distanceToBoundary)
+ bytes = distanceToBoundary;
+ }
+ }
+
// if header options will be added, this could reduce the number of data bytes allowed for this segment,
// because following condition must to be respected:
// bytes + options_len <= snd_mss
@@ -980,9 +1611,36 @@ uint32_t TcpConnection::sendSegment(uint32_t bytes)
ASSERT(options_len < state->snd_mss);
- if (bytes + options_len > state->snd_mss)
+ // An RFC 4821 probe is deliberately larger than snd_mss -- being larger is the
+ // whole experiment -- so it is the one segment the MSS clamp must leave alone.
+ if (state->mtupProbeBytes == 0 && bytes + options_len > state->snd_mss)
bytes = state->snd_mss - options_len;
+ // A retransmission never extends past the previously sent high-water mark
+ // in the same segment: Linux retransmits skbs from the rtx queue (possibly
+ // collapsed together, but only from already-SENT data); unsent data goes
+ // out in its own segments behind it (syn-data-only-syn-acked pins the
+ // 1420-byte TFO fallback retransmit NOT swallowing 40 fresh bytes, which
+ // also mis-aligned every following segment off the golden's boundaries).
+ if (seqLess(state->snd_nxt, state->snd_max) && bytes > state->snd_max - state->snd_nxt)
+ bytes = state->snd_max - state->snd_nxt;
+
+ // ... and it honors the ORIGINAL segment boundaries: Linux's rtx queue
+ // holds whole skbs, and tcp_retrans_try_collapse merges only ENTIRE
+ // adjacent sent skbs that fit cur_mss together -- it never splits the
+ // next skb to top a retransmit up to the MSS. Cap at the largest recorded
+ // transmission boundary inside the budget (syn-data-only-syn-acked pins
+ // the RACK retransmit of the 1420-byte TFO payload staying 1420 bytes).
+ if (seqLess(state->snd_nxt, state->snd_max) && bytes > 0 && rexmitQueue != nullptr) {
+ const auto& starts = rexmitQueue->xmitSegmentStarts;
+ auto it = starts.upper_bound(state->snd_nxt + bytes);
+ if (it != starts.begin()) {
+ uint32_t b = *std::prev(it);
+ if (seqGreater(b, state->snd_nxt) && seqLess(b, state->snd_nxt + bytes))
+ bytes = b - state->snd_nxt;
+ }
+ }
+
uint32_t sentBytes = bytes;
// send one segment of 'bytes' bytes from snd_nxt, and advance snd_nxt
@@ -998,19 +1656,77 @@ uint32_t TcpConnection::sendSegment(uint32_t bytes)
tcpHeader->setAckBit(true);
tcpHeader->setWindow(updateRcvWnd());
- // ECN
- if (state->ect && state->sndCwr) {
+ // ECN. AccECN connections repurpose cwrBit as part of the post-handshake ACE counter
+ // (encoded later in sendToIP()); classic CWR-on-reduction must not also write it here.
+ if (state->ect && state->sndCwr && !state->accEcnNegotiated) {
tcpHeader->setCwrBit(true);
EV_INFO << "set CWR bit\n";
state->sndCwr = false;
}
- // TODO when to set PSH bit?
// TODO set URG bit if needed
ASSERT(bytes == tcpSegment->getByteLength());
state->snd_nxt += bytes;
+ // MSG_EOR: set PSH when this segment's last byte lands exactly
+ // on a still-pending record boundary -- signals the peer to hand the data up to
+ // its application without waiting for more, mirroring a real PSH-at-record-
+ // boundary policy. A boundary not yet reached (this segment fell short, e.g.
+ // clamped further by the MSS/options budget above) stays pending in eorSeqNums
+ // and is retried by the connection's next sendSegment() call.
+ if (eorSeqNums.count(state->snd_nxt))
+ tcpHeader->setPshBit(true);
+
+ // TCP_CORK / MSG_MORE: a corked partial being flushed carries PSH when its
+ // producing write lacked MSG_MORE, or when the cork timer forced the flush
+ // (Linux tcp_mark_push / tcp_write_wakeup). sendData sets pushThisSegment.
+ if (state->pushThisSegment)
+ tcpHeader->setPshBit(true);
+
+ // Linux parity (pushSegmentsOnWriteBoundary): Linux tags the tail skb of
+ // every write with PSH at sendmsg time (tcp_mark_push), so the segment
+ // carrying a write's last byte is PSHed even when the NEXT write is
+ // already buffered behind it, and a retransmission of that segment keeps
+ // the flag. The boundaries were recorded per-write in
+ // enqueueSendCommandData (pushSeqNums); snd_nxt has just advanced past
+ // this segment's payload, so a hit means this segment ends a write.
+ // PSH is inert on INET's own receiver (it only logs "ignoring"), so this is
+ // pure wire-realism; default-off pending a maintainer-gated flip.
+ //
+ // Skip a corked partial being flushed (corkFlush = explicit uncork/nodelay/timer,
+ // corkedDataPending = a previously-held partial going out now): its PSH is fully
+ // governed by the cork rule above (pushThisSegment). MSG_MORE writes never
+ // record a boundary, matching Linux's mark_push skip for MSG_MORE.
+ if (state->pushOnWriteBoundary && bytes > 0 && pushSeqNums.count(state->snd_nxt)
+ && !state->corkFlush && !state->corkedDataPending)
+ tcpHeader->setPshBit(true);
+
+ // Linux forced_push (tcp_sendmsg): mid-write PSH boundaries were computed at
+ // enqueue time (see enqueueSendCommandData); a hit means this segment's last
+ // byte is such a boundary. Corked partials keep their own PSH rule (above).
+ if (state->pushOnWriteBoundary && bytes > 0 && !state->corkFlush && !state->corkedDataPending
+ && forcedPushSeqNums.count(state->snd_nxt))
+ tcpHeader->setPshBit(true);
+
+ // MSG_ZEROCOPY: fire a completion notification for every
+ // pending zerocopy SEND whose data has now been transmitted (its boundary seq
+ // is at or behind the just-advanced snd_nxt) -- unlike MSG_EOR's clamp, a
+ // single segment may legitimately span (and thus complete) several small
+ // zerocopy-marked SENDs at once, so this drains all that are now covered
+ // rather than checking for one exact match.
+ while (!zerocopySeqNums.empty() && !seqGreater(zerocopySeqNums.begin()->first, state->snd_nxt)) {
+ uint32_t zerocopyId = zerocopySeqNums.begin()->second;
+ zerocopySeqNums.erase(zerocopySeqNums.begin());
+ EV_INFO << "Notifying app: ZEROCOPY_COMPLETION id=" << zerocopyId << "\n";
+ auto *completionIndication = new Indication("ZerocopyCompletion", TCP_I_ZEROCOPY_COMPLETION);
+ auto *completionInfo = new TcpZerocopyCompletionInfo();
+ completionInfo->setZerocopyId(zerocopyId);
+ completionIndication->addTag()->setSocketId(socketId);
+ completionIndication->setControlInfo(completionInfo);
+ sendToApp(completionIndication);
+ }
+
// check if afterRto bit can be reset
if (state->afterRto && seqGE(state->snd_nxt, state->snd_max))
state->afterRto = false;
@@ -1022,8 +1738,7 @@ uint32_t TcpConnection::sendSegment(uint32_t bytes)
}
// if sack_enabled copy region of tcpHeader to rexmitQueue
- if (state->sack_enabled)
- rexmitQueue->enqueueSentData(old_snd_nxt, state->snd_nxt);
+ rexmitQueue->enqueueSentData(old_snd_nxt, state->snd_nxt);
// add header options and update header length (from tcpseg_temp)
for (uint i = 0; i < tmpTcpHeader->getHeaderOptionArraySize(); i++)
@@ -1045,17 +1760,337 @@ uint32_t TcpConnection::sendSegment(uint32_t bytes)
state->queueUpdate = true;
}
+ // TCP_NOTSENT_LOWAT: independent low-water-mark check on the
+ // not-yet-transmitted portion of the queue (snd_nxt has just advanced past this
+ // segment, above). Disarmed/re-armed separately from sendQueueLimit/queueUpdate.
+ if (state->notsentLowat != (uint32_t)-1 && !state->notsentLowatUpdate) {
+ uint32_t notsentBytes = sendQueue->getBytesAvailable(state->snd_nxt);
+ if (notsentBytes <= state->notsentLowat) {
+ sendIndicationToApp(TCP_I_SEND_MSG, notsentBytes);
+ state->notsentLowatUpdate = true;
+ }
+ }
+
+ // Minshall bookkeeping (Linux tcp_minshall_update, called only for NEW
+ // data in tcp_write_xmit): a freshly sent sub-MSS segment records its end
+ // seq so Nagle can hold the NEXT partial until this one is acked.
+ // Retransmissions (snd_nxt at or below the old snd_max) don't count.
+ // "Small" is judged against what THIS segment could have carried
+ // (Linux compares skb->len to pcount * mss_now, the options-adjusted
+ // MSS) -- not against snd_effmss, which still includes option headroom.
+ if (bytes > 0 && bytes + options_len < state->snd_mss && seqGreater(state->snd_nxt, state->snd_max))
+ state->snd_sml = state->snd_nxt;
+
// remember highest seq sent (snd_nxt may be set back on retransmission,
// but we'll need snd_max to check validity of ACKs -- they must ack
// something we really sent)
- if (seqGreater(state->snd_nxt, state->snd_max))
+ if (seqGreater(state->snd_nxt, state->snd_max)) {
state->snd_max = state->snd_nxt;
+ emit(sndMaxSignal, state->snd_max);
+ }
+
+ // Track peak segments in flight (Linux max_packets_out) for the RFC 5681
+ // cwnd-limited slow-start gate: an application-limited flow that never fills
+ // the congestion window must not be allowed to inflate it. Round up so a
+ // partial trailing segment counts as a whole packet (Linux accounts in
+ // packets, not bytes).
+ if (state->snd_mss > 0) {
+ uint32_t packetsOut = (state->snd_max - state->snd_una + state->snd_mss - 1) / state->snd_mss;
+ if (packetsOut > state->maxPacketsOut)
+ state->maxPacketsOut = packetsOut;
+ }
+
+ updateSndbufLimitedChrono(); // this send may have drained the queue
return sentBytes;
}
+void TcpConnection::enqueueSendCommandData(Packet *packet)
+{
+ // A zero-length SEND carries no data to queue and must not reach
+ // TcpSendQueue::enqueueAppData(), whose peekDataAt(B(0), 0) throws "Returning
+ // an empty chunk is not allowed". It is not a malformed command: send(fd, x, 0)
+ // is a legal no-op, and sendto(fd, x, 0, MSG_FASTOPEN) is how an application
+ // asks for nothing but a Fast Open cookie. Only the SYN_SENT fast-open branch
+ // used to screen for it, so the same syscall crashed whenever the SYN had not
+ // been deferred -- i.e. exactly when no cookie was cached, which is when a
+ // bare cookie request is what the application wanted.
+ if (packet->getByteLength() == 0) {
+ EV_DETAIL << "Zero-length SEND: nothing to queue\n";
+ delete packet;
+ return;
+ }
+
+ // TCP_INFO trio (busy_time): read-only bookkeeping -- if the connection was
+ // fully idle (nothing outstanding, nothing queued) before this SEND, it becomes
+ // busy now. See processAckInEstabEtc() for the matching "back to idle" exit.
+ if (state->busyStartTime < SIMTIME_ZERO && state->snd_una == state->snd_max
+ && sendQueue->getBytesAvailable(state->snd_nxt) == 0)
+ {
+ state->busyStartTime = simTime();
+ }
+
+ bool eor = packet->findTag() != nullptr;
+ bool zerocopy = packet->findTag() != nullptr;
+ // TCP_CORK / MSG_MORE: MSG_MORE corks this one send's trailing partial. A write
+ // WITHOUT MSG_MORE while corking is active marks the held tail for PSH on flush
+ // (Linux tcp_mark_push). msgMoreThisSend is consumed at the top of sendData().
+ bool msgMore = packet->findTag() != nullptr;
+ state->msgMoreThisSend = msgMore;
+ if (!msgMore && (state->tcp_cork || state->corkedDataPending))
+ state->pushHeldPartial = true;
+ uint32_t writeStartSeq = sendQueue->getBufferEndSeq(); // Linux tp->write_seq before this write
+ sendQueue->enqueueAppData(packet);
+ if (eor) {
+ uint32_t boundarySeq = sendQueue->getBufferEndSeq();
+ eorSeqNums.insert(boundarySeq);
+ EV_DETAIL << "MSG_EOR: recorded record boundary at seq=" << boundarySeq << "\n";
+ }
+ // Linux tcp_mark_push tags the tail skb of every write (without MSG_MORE)
+ // AT WRITE TIME -- the PSH survives later writes queuing behind it and is
+ // retained on retransmission. Recording the boundary here (instead of a
+ // buffer-drained check at transmit time) is what keeps the last segment of
+ // a write PSH-marked even when the next write is already buffered
+ // Linux forced_push (tcp_sendmsg copy loop): while a large write is being
+ // copied into the send queue, every time an skb fills with more than
+ // max_window/2 of data beyond the last PSH mark, that skb is PSH-marked
+ // (tcp_mark_push) so the receiver keeps delivering without waiting for the
+ // whole write to drain. skb fill geometry: size_goal quantizes
+ // tcp_bound_to_half_wnd(max_window/2, aligned by Linux's ALIGN() with the
+ // PMTU-derived mss_cache 1460) down to whole MSS units. The mark's PSH
+ // surfaces on the marked skb's SECOND mss slice, i.e. the wire segment
+ // ending at skbStart + 2*mss.
+ if (state->pushOnWriteBoundary && !msgMore && state->max_window > 0) {
+ if (seqLess(state->pushed_seq, state->snd_una))
+ state->pushed_seq = state->snd_una; // lazy seed: first write of a connection
+ uint32_t halfWnd = state->max_window >> 1;
+ const uint32_t mssCache = 1460; // Linux tp->mss_cache (IPv4 PMTU 1500 - 40)
+ uint32_t alignQ = (halfWnd + mssCache - 1) & ~(mssCache - 1); // Linux ALIGN() verbatim (the macro assumes a power of 2; Linux applies it to mss_cache anyway, so replicate bit-for-bit)
+ uint32_t sizeGoal = std::max((alignQ / state->snd_mss) * state->snd_mss, state->snd_mss);
+ uint32_t writeEndSeq = sendQueue->getBufferEndSeq();
+ for (uint32_t f = writeStartSeq + sizeGoal; seqLE(f, writeEndSeq); f += sizeGoal) {
+ if (seqGreater(f, state->pushed_seq + halfWnd)) {
+ uint32_t pshSeq = f - sizeGoal + 2 * state->snd_mss;
+ forcedPushSeqNums.insert(pshSeq);
+ state->pushed_seq = f;
+ EV_DETAIL << "forced_push: recorded mid-write PSH boundary at seq=" << pshSeq << "\n";
+ }
+ }
+ }
+ if (state->pushOnWriteBoundary && !msgMore) {
+ pushSeqNums.insert(sendQueue->getBufferEndSeq());
+ state->pushed_seq = sendQueue->getBufferEndSeq(); // Linux tcp_push -> tcp_mark_push on the write's tail skb
+ }
+
+ if (zerocopy) {
+ uint32_t boundarySeq = sendQueue->getBufferEndSeq();
+ uint32_t zerocopyId = nextZerocopyId++;
+ zerocopySeqNums[boundarySeq] = zerocopyId;
+ EV_DETAIL << "MSG_ZEROCOPY: recorded pending completion id=" << zerocopyId << " at seq=" << boundarySeq << "\n";
+ }
+
+ updateSndbufLimitedChrono(); // fresh unsent data: the starved interval (if any) ends
+}
+
+uint32_t TcpConnection::getDataSndUna() const
+{
+ // Only the Fast Open server's pre-handshake-ACK window applies here: no other
+ // state can have unacknowledged data while snd_una still sits on the SYN's own
+ // sequence number. See the declaration for why the ISS slot must be skipped.
+ if (fsm.getState() == TCP_S_SYN_RCVD && state->snd_una == state->iss)
+ return state->iss + 1;
+ return state->snd_una;
+}
+
+void TcpConnection::releaseRcvBufOccupancy(uint64_t readBytes)
+{
+ // Linux frees an skb -- and with it the WHOLE truesize it was charged -- only
+ // once the application has copied all of it, so a partially read skb keeps
+ // costing the buffer everything it did before.
+ while (readBytes > 0 && !rcvSkbChain.empty()) {
+ auto& head = rcvSkbChain.front();
+ if (readBytes >= head.first) {
+ readBytes -= head.first;
+ rcvBufOccupancy -= std::min(head.second, rcvBufOccupancy);
+ rcvSkbChain.pop_front();
+ }
+ else {
+ head.first -= (uint32_t)readBytes;
+ readBytes = 0;
+ }
+ }
+}
+
+uint32_t TcpConnection::gsoBurstSegments(uint32_t congestionWindow, uint32_t bytesInFlight, uint32_t effectiveMss) const
+{
+ if (effectiveMss == 0)
+ return 1;
+ // How many segments Linux hands to the NIC as one GSO super-segment, which is
+ // what decides where the forced PSH lands (tcp_write_xmit's
+ // min(cwnd_quota, tcp_tso_segs) fed to tcp_mss_split_point).
+ //
+ // tcp_cwnd_test: never more than half the congestion window, so a second burst
+ // can be scheduled while the first is in flight.
+ uint32_t cwndSegs = congestionWindow / effectiveMss;
+ uint32_t inFlightSegs = bytesInFlight / effectiveMss;
+ if (cwndSegs == 0 || inFlightSegs >= cwndSegs)
+ return 1;
+ uint32_t cwndQuota = std::min(std::max(cwndSegs / 2, 1u), cwndSegs - inFlightSegs);
+
+ // tcp_tso_autosize: a burst is what the pacing rate lets the connection emit in
+ // one millisecond-ish tick (rate >> sk_pacing_shift), floored at two segments.
+ // At ordinary RTTs that floor is what binds; only a sub-millisecond path makes
+ // the rate large enough for the cwnd quota above to become the limit.
+ simtime_t srtt = tcpAlgorithm != nullptr ? tcpAlgorithm->getSrtt() : SIMTIME_ZERO;
+ if (srtt <= SIMTIME_ZERO)
+ // No RTT sample yet. Linux leaves the rate undivided in that case
+ // (tcp_update_pacing_rate's `if (likely(tp->srtt_us))`), which puts it far
+ // above anything a burst could reach -- so the cwnd quota decides alone.
+ return std::max(1u, cwndQuota);
+ // Linux's slow-start pacing ratio (sysctl tcp_pacing_ss_ratio = 200%). The
+ // congestion-avoidance ratio (120%) differs too little to move the segment count
+ // across the two-segment floor, so the distinction is not worth carrying here.
+ double bytesPerBurst = (2.0 * congestionWindow / srtt.dbl()) / 1024.0; // Linux sk_pacing_shift
+ uint32_t tsoSegs = std::max(2, (uint32_t)(bytesPerBurst / effectiveMss));
+ return std::max(1u, std::min(cwndQuota, tsoSegs));
+}
+
+uint32_t TcpConnection::mtuHeaderOverhead() const
+{
+ const uint32_t netHeaderLen = (remoteAddr.getType() == L3Address::IPv6) ? 40 : 20;
+ // Linux's tcp_header_len: the bare header plus the options every established
+ // segment carries. Only timestamps qualify -- SACK blocks come and go, which is
+ // exactly why tcp_mtu_probe refuses to run while any are in play.
+ const uint32_t tcpHeaderLen = (TCP_MIN_HEADER_LENGTH + (state->ts_enabled ? TCP_OPTION_TS_SIZE : B(0))).get();
+ return netHeaderLen + tcpHeaderLen;
+}
+
+uint32_t TcpConnection::mtuToMss(uint32_t mtu) const
+{
+ uint32_t overhead = mtuHeaderOverhead();
+ return mtu > overhead ? mtu - overhead : 0;
+}
+
+uint32_t TcpConnection::mssToMtu(uint32_t mss) const
+{
+ return mss + mtuHeaderOverhead();
+}
+
+void TcpConnection::mtupInit()
+{
+ if (!state->mtupEnabled)
+ return;
+ // Linux runs this from tcp_init_transfer, i.e. AFTER the child's MSS has been
+ // synced from the route -- which is why arming the search never disturbs the
+ // MSS the connection is already using. The upper bound is the largest segment
+ // the peer said it would accept; the lower bound is the one we assume works.
+ uint32_t peerMss = state->peerAdvertisedMss > 0 ? state->peerAdvertisedMss : state->snd_mss;
+ state->mtupSearchHigh = peerMss + mtuHeaderOverhead();
+ state->mtupSearchLow = mssToMtu((uint32_t)tcpMain->par("baseMss"));
+ state->mtupProbeSize = 0;
+ int pathMtuPar = pathMtuSockopt > 0 ? pathMtuSockopt : (int)tcpMain->par("pathMtu");
+ state->pathMtu = pathMtuPar > 0 ? (uint32_t)pathMtuPar : mssToMtu(state->snd_mss);
+ EV_DETAIL << "RFC 4821: MTU search armed, low=" << state->mtupSearchLow
+ << " high=" << state->mtupSearchHigh << " pathMtu=" << state->pathMtu << "\n";
+}
+
+uint32_t TcpConnection::mtuProbeBytes(uint32_t buffered, uint32_t congestionWindow) const
+{
+ // Linux tcp_mtu_probe's entry guards: one probe at a time, no probing while
+ // recovering, and none while SACK blocks are in play (their variable header
+ // length would make the probe's size mean nothing).
+ if (!state->mtupEnabled || state->mtupProbeSize != 0 || state->lossRecovery || state->afterRto)
+ return 0;
+ if (state->snd_sacks > 0 || state->rcv_sacks > 0)
+ return 0;
+ // "Have enough cwnd": Linux counts packets, so the byte-valued cwnd here has to
+ // clear the same 11 segments.
+ if (congestionWindow < 11 * state->snd_effmss)
+ return 0;
+
+ uint32_t probeMtu = (state->mtupSearchHigh + state->mtupSearchLow) / 2;
+ uint32_t probeMss = mtuToMss(probeMtu);
+ if (probeMss <= state->snd_effmss || probeMss > mtuToMss(state->mtupSearchHigh))
+ return 0;
+ // Once the bracket is this narrow the remaining gain no longer pays for a probe
+ // (Linux sysctl tcp_probe_threshold, default 8).
+ if (state->mtupSearchHigh - state->mtupSearchLow < 8)
+ return 0;
+
+ // The probe must be recoverable without an RTO: enough further data has to
+ // follow it that a loss is signalled by duplicate ACKs instead.
+ uint32_t sizeNeeded = probeMss + (state->reordering + 1) * state->snd_effmss;
+ if (buffered < sizeNeeded || state->snd_wnd < sizeNeeded)
+ return 0;
+ if (seqGreater(state->snd_nxt + sizeNeeded, getDataSndUna() + state->snd_wnd))
+ return 0;
+ // Sending the probe costs one extra packet's worth of window; wait for the pipe
+ // to drain unless it is empty, where waiting would stall the connection outright.
+ uint32_t inFlight = tcpAlgorithm->getBytesInFlight();
+ if (inFlight > 0 && inFlight + 2 * state->snd_effmss > congestionWindow)
+ return 0;
+
+ return probeMss;
+}
+
+void TcpConnection::mtupProbeFailed()
+{
+ // Linux tcp_mtup_probe_failed: loss while a probe is outstanding says the path
+ // will not carry that size, so it becomes the new ceiling. Without this the
+ // retransmitted data is eventually acknowledged like any other and the probe
+ // would be recorded as a SUCCESS -- raising the floor to a size that just
+ // demonstrably failed.
+ if (state->mtupProbeSize == 0)
+ return;
+ EV_INFO << "RFC 4821: probe of MTU " << state->mtupProbeSize
+ << " was lost; search high now " << (state->mtupProbeSize - 1) << "\n";
+ state->mtupSearchHigh = state->mtupProbeSize - 1;
+ state->mtupProbeSize = 0;
+}
+
+void TcpConnection::mtupProbeSucceeded()
+{
+ uint32_t probeMtu = state->mtupProbeSize;
+ state->mtupSearchLow = probeMtu;
+ state->mtupProbeSize = 0;
+ // Linux re-syncs the MSS from the route here: the path MTU is still the ceiling,
+ // but the search's lower bound is now what the connection may actually use.
+ uint32_t synced = std::min(mtuToMss(state->pathMtu), mtuToMss(state->mtupSearchLow));
+ if (synced > state->snd_mss) {
+ state->snd_mss = synced;
+ state->snd_effmss = calculateEffectiveMss();
+ }
+ EV_INFO << "RFC 4821: probe of MTU " << probeMtu << " acknowledged, snd_mss now "
+ << state->snd_mss << " (search low=" << state->mtupSearchLow
+ << " high=" << state->mtupSearchHigh << ")\n";
+}
+
+int TcpConnection::deriveLinuxCaState() const
+{
+ if (state->afterRto)
+ return 4; // TCP_CA_Loss
+ if (state->lossRecovery)
+ return 3; // TCP_CA_Recovery
+ if (state->sndCwr)
+ return 2; // TCP_CA_CWR
+ // TCP_CA_Disorder: SACK/dup information has arrived (segments sit above
+ // snd_una) but not enough to enter recovery yet -- Linux tcp_fastretrans_alert
+ // holds ca_state at Disorder while sacked_out > 0 without a confirmed loss.
+ // sackedBytes is kept current on both the SACK and the cumulative-ACK path, so
+ // this reverts to Open as soon as snd_una catches up to the SACKed data.
+ if (state->sack_enabled && state->sackedBytes > 0)
+ return 1; // TCP_CA_Disorder
+ return 0; // TCP_CA_Open
+}
+
bool TcpConnection::sendData(uint32_t congestionWindow)
{
+ // MSG_MORE corks the trailing partial only for THIS send-driven sendData().
+ // Read-and-clear it up front so it never leaks to the ACK-driven or cork-timer
+ // send paths (there, only the persistent TCP_CORK holds).
+ bool msgMoreHold = state->msgMoreThisSend;
+ state->msgMoreThisSend = false;
+
// we'll start sending from snd_max, if not after RTO
if (!state->afterRto)
state->snd_nxt = state->snd_max;
@@ -1071,19 +2106,44 @@ bool TcpConnection::sendData(uint32_t congestionWindow)
if (buffered == 0)
return false;
- // maxWindow is minimum of snd_wnd and congestionWindow (snd_cwnd)
- uint32_t maxWindow = std::min(state->snd_wnd, congestionWindow);
-
- // effectiveWindow: number of bytes we're allowed to send now
- int64_t effectiveWin = (int64_t)maxWindow - (state->snd_nxt - state->snd_una);
+ // The receiver may shrink its advertised window (or close it entirely) while
+ // data is still in flight, so snd_wnd can legitimately be smaller than the
+ // unacknowledged range -- e.g. during zero-window probing. Saturate at zero
+ // instead of underflowing the unsigned subtraction.
+ uint32_t unackedInWindow = state->snd_nxt - getDataSndUna();
+ uint32_t spaceLeftInSendWindow = state->snd_wnd > unackedInWindow ? state->snd_wnd - unackedInWindow : 0;
+ uint32_t bytesInFlight = tcpAlgorithm->getBytesInFlight();
+ uint32_t spaceLeftInCongestionWindow = bytesInFlight >= congestionWindow ? 0 : congestionWindow - bytesInFlight;
+
+ uint32_t allowedToSend = std::min(spaceLeftInSendWindow, spaceLeftInCongestionWindow);
+ // TCP_INFO trio (rwnd_limited): read-only bookkeeping, consulted only by
+ // TcpStatusInfo -- never influences the send decision below. "rwnd-limited"
+ // here means: there is more buffered data than can be sent right now, and the
+ // peer's advertised window (not the congestion window) is the binding
+ // constraint.
+ bool rwndBinding = (state->snd_wnd < congestionWindow)
+ && ((int64_t)buffered > std::max(allowedToSend, 0));
+ if (rwndBinding) {
+ if (state->rwndLimitedStartTime < SIMTIME_ZERO)
+ state->rwndLimitedStartTime = simTime();
+ }
+ else if (state->rwndLimitedStartTime >= SIMTIME_ZERO) {
+ state->rwndLimitedAccumulated += simTime() - state->rwndLimitedStartTime;
+ state->rwndLimitedStartTime = -1;
+ }
- if (effectiveWin <= 0) {
- EV_WARN << "Effective window is zero (advertised window " << state->snd_wnd
+ if (allowedToSend <= 0) {
+ EV_WARN << "AllowedToSend is zero (advertised window " << state->snd_wnd
<< ", congestion window " << congestionWindow << "), cannot send.\n";
return false;
}
- uint32_t bytesToSend = std::min(buffered, (uint32_t)effectiveWin);
+ if (allowedToSend < state->snd_effmss && buffered > allowedToSend) {
+ EV_WARN << "Not sending to prevent Silly Window Syndrome.\n";
+ return false;
+ }
+
+ uint32_t bytesToSend = std::min(buffered, (uint32_t)allowedToSend);
// make a temporary tcp header for detecting tcp options length (copied from 'TcpConnection::sendSegment(uint32_t bytes)' )
const auto& tmpTcpHeader = makeShared();
@@ -1096,13 +2156,61 @@ bool TcpConnection::sendData(uint32_t congestionWindow)
uint32_t old_snd_nxt = state->snd_nxt;
// start sending 'bytesToSend' bytes
- EV_INFO << "May send " << bytesToSend << " bytes (effectiveWindow " << effectiveWin << ", in buffer " << buffered << " bytes)\n";
+ EV_INFO << "May send " << bytesToSend << " bytes (allowedToSend " << allowedToSend << ", in buffer " << buffered << " bytes)\n";
+
+ // RFC 4821: spend the head of this send on one oversized probe segment. It
+ // carries data that was going out anyway, so a probe that gets through costs
+ // nothing; one that does not is repaired by the ordinary loss machinery.
+ uint32_t probeBytes = mtuProbeBytes(buffered, congestionWindow);
+ if (probeBytes > 0 && probeBytes <= bytesToSend) {
+ state->mtupProbeSeqStart = state->snd_nxt;
+ state->mtupProbeBytes = probeBytes;
+ uint32_t sentProbe = sendSegment(probeBytes);
+ state->mtupProbeBytes = 0;
+ state->mtupProbeSeqEnd = state->mtupProbeSeqStart + sentProbe;
+ state->mtupProbeSize = mssToMtu(sentProbe);
+ // No cwnd adjustment: Linux decrements it by one because its window counts
+ // PACKETS and the probe replaces several of them with one oversized packet.
+ // Here the window counts bytes, and the probe already costs exactly the
+ // bytes it carries.
+ EV_INFO << "RFC 4821: sent MTU probe of " << sentProbe << " bytes (MTU "
+ << state->mtupProbeSize << ")\n";
+ bytesToSend -= sentProbe;
+ allowedToSend -= sentProbe;
+ buffered -= sentProbe;
+ }
// send whole segments
+ uint32_t fullSegIdx = 0;
+ uint32_t burstSegs = gsoBurstSegments(congestionWindow, bytesInFlight, effectiveMss);
+ uint32_t oldSndMax = state->snd_max;
while (bytesToSend >= effectiveMss) {
+ // Retransmitted segments (below the pre-send high-water mark) are
+ // their own skbs in Linux and never join a new-data GSO chunk: the
+ // two-segment pairing below counts NEW data only, so a leading
+ // retransmit doesn't shift the PSH parity (syn-data-only-syn-acked:
+ // P. 1:1421 rexmit, then chunks (1421:2881,2881:4341^P)...).
+ bool newData = seqGE(state->snd_nxt, oldSndMax);
+ // Linux forces PSH on every multi-segment GSO burst (tcp_transmit_skb:
+ // tcp_skb_pcount(skb) > 1). After the GSO split the flag sits on the
+ // burst's LAST wire slice, so a write spanning several MSS carries PSH
+ // once per burst -- on top of the write-tail PSH from pushSeqNums.
+ // Same wire-realism gate as the other Linux PSH rules; retransmits
+ // and other send paths are untouched (a rexmitted slice loses the
+ // forced PSH in Linux too, as the split skb's pcount drops to 1).
+ // A single-segment burst carries no forced PSH: the flag is Linux's marker
+ // that a GSO super-segment ended, and an skb holding one segment has
+ // tcp_skb_pcount() == 1.
+ state->pushThisSegment = state->pushOnWriteBoundary && newData && burstSegs > 1
+ && (fullSegIdx % burstSegs == burstSegs - 1);
uint32_t sentBytes = sendSegment(effectiveMss);
+ state->pushThisSegment = false;
+ if (newData)
+ fullSegIdx++;
ASSERT(bytesToSend >= sentBytes);
bytesToSend -= sentBytes;
+ allowedToSend -= sentBytes;
+ buffered -= sentBytes;
}
if (bytesToSend > 0) {
@@ -1110,13 +2218,63 @@ bool TcpConnection::sendData(uint32_t congestionWindow)
// yet been acknowledged, small segments cannot be sent until the outstanding
// data is acknowledged.
bool unacknowledgedData = (state->snd_una != state->snd_max);
+ // The unacknowledged SYN's sequence-number slot is not data: a TCP
+ // Fast Open server sending its response from SYN_RCVD still has
+ // snd_una at the SYN (iss) with snd_max = iss+1 -- Nagle must not
+ // hold the sub-MSS response hostage to a handshake segment (Linux's
+ // nagle test walks the data write queue, where the SYN-ACK never
+ // appears, so it sends immediately too).
+ if (unacknowledgedData && fsm.getState() == TCP_S_SYN_RCVD
+ && state->snd_una == state->iss && state->snd_max == state->iss + 1)
+ unacknowledgedData = false;
bool containsFin = state->send_fin && (state->snd_nxt + bytesToSend) == state->snd_fin_seq;
- if (state->nagle_enabled && unacknowledgedData && !containsFin)
- EV_WARN << "Cannot send (last) segment due to Nagle, not enough data for a full segment\n";
- else
+ // TCP_CORK / MSG_MORE hold the trailing sub-MSS partial (full segments were
+ // already sent by the loop above). Unlike Nagle, corking holds even with an
+ // empty pipe. A flush in progress (corkFlush: uncork/nodelay/timer) bypasses
+ // both holds.
+ bool corkHold = (state->tcp_cork || msgMoreHold) && !state->corkFlush
+ && !containsFin && buffered < state->snd_effmss;
+ // Minshall's variant of the Nagle check (Linux tcp_nagle_check +
+ // tcp_minshall_check): a trailing partial is held only while an
+ // earlier SMALL segment is still unacknowledged; a pipe full of
+ // nothing but full-MSS segments never delays it
+ // (client_accecn_options_lost pins the 976-byte tail of a 3000-byte
+ // write leaving back-to-back with its two full siblings).
+ bool unackedSmallSegment = seqGreater(state->snd_sml, state->snd_una)
+ && seqLE(state->snd_sml, state->snd_nxt);
+ bool nagleHold = state->nagle_enabled && unacknowledgedData && unackedSmallSegment
+ && !containsFin && buffered < state->snd_effmss && !state->corkFlush;
+ if (allowedToSend < state->snd_effmss && buffered > allowedToSend)
+ EV_WARN << "Not sending to prevent Silly Window Syndrome.\n";
+ else if (corkHold || nagleHold) {
+ if (corkHold)
+ state->corkedDataPending = true;
+ EV_WARN << "Holding partial segment ("
+ << (corkHold ? "TCP_CORK/MSG_MORE" : "Nagle") << ")\n";
+ }
+ else {
+ // If this partial is a flush of previously-corked data, it may carry PSH:
+ // when the producing write(s) lacked MSG_MORE (pushHeldPartial) or the
+ // flush is the cork timer (forcePushHeld). sendSegment reads pushThisSegment.
+ bool flushingCorked = state->corkedDataPending || state->corkFlush;
+ state->pushThisSegment = flushingCorked && (state->pushHeldPartial || state->forcePushHeld);
sendSegment(bytesToSend);
+ state->pushThisSegment = false;
+ state->corkedDataPending = false;
+ state->pushHeldPartial = false;
+ }
}
+ // Cork (RTO/probe) timer: arm it only while a corked partial is withheld AND
+ // nothing is in flight (Linux ICSK_TIME_PROBE0 needs packets_out == 0); an
+ // incoming ACK re-runs sendData and flushes otherwise. Disarm as soon as the
+ // partial goes out or data becomes outstanding. A Nagle hold always has
+ // snd_una != snd_max, so it never arms this timer.
+ if (state->corkedDataPending && state->snd_una == state->snd_max)
+ tcpAlgorithm->scheduleCorkTimer();
+ else
+ tcpAlgorithm->cancelCorkTimer();
+
if (old_snd_nxt == state->snd_nxt)
return false; // no data sent
@@ -1126,7 +2284,7 @@ bool TcpConnection::sendData(uint32_t congestionWindow)
tcpAlgorithm->ackSent();
if (state->sack_enabled && state->lossRecovery && old_highRxt != state->highRxt) {
- // Note: Restart of REXMIT timer on retransmission is not part of RFC 2581, however optional in RFC 3517 if sent during recovery.
+ // Note: Restart of REXMIT timer on retransmission is not part of RFC 5681, however optional in RFC 6675 if sent during recovery.
EV_DETAIL << "Retransmission sent during recovery, restarting REXMIT timer.\n";
tcpAlgorithm->restartRexmitTimer();
}
@@ -1136,41 +2294,90 @@ bool TcpConnection::sendData(uint32_t congestionWindow)
return true;
}
+void TcpConnection::flushCorkedData(bool forcePush)
+{
+ // Force out a partial segment currently withheld by TCP_CORK / MSG_MORE
+ // (uncork, TCP_NODELAY, or the cork timer). corkFlush makes sendData's trailing
+ // partial bypass both the cork and Nagle holds; forcePush (timer path only)
+ // makes the flushed partial carry PSH. msgMoreThisSend is false here (no new
+ // SEND), so nothing re-corks. Reuse sendCommandInvoked()'s idle-restart cwnd
+ // path into conn->sendData().
+ state->corkFlush = true;
+ state->forcePushHeld = forcePush;
+ tcpAlgorithm->sendCommandInvoked();
+ state->corkFlush = false;
+ state->forcePushHeld = false;
+}
+
bool TcpConnection::sendProbe()
{
- // we'll start sending from snd_max
- state->snd_nxt = state->snd_max;
+ // Linux tcp_xmit_probe_skb: a zero-window probe is a DATALESS segment
+ // with seq = SND.UNA - 1. It provokes a pure ACK carrying the peer's
+ // current window without consuming sequence space -- the old 1-byte BSD
+ // persist style consumed a real byte and dragged the RTO machinery into
+ // the probing (slow-start-after-win-update pins "26000:26000(0)", i.e.
+ // snd_una-1 dataless, and tcp_persist_1's trace was updated in step).
+ EV_INFO << "Sending zero-window probe, seq=" << (state->snd_una - 1) << "\n";
- // check we have 1 byte to send
- if (sendQueue->getBytesAvailable(state->snd_nxt) == 0) {
- EV_WARN << "Cannot send probe because send buffer is empty\n";
- return false;
- }
+ const auto& tcpHeader = makeShared();
+ tcpHeader->setSequenceNo(state->snd_una - 1);
+ tcpHeader->setAckBit(true);
+ tcpHeader->setAckNo(state->rcv_nxt);
+ updateRcvWnd();
+ tcpHeader->setWindow(state->rcv_wnd >> state->rcv_wnd_scale);
+ writeHeaderOptions(tcpHeader);
+ Packet *fp = new Packet("ZeroWindowProbe");
+ sendToIP(fp, tcpHeader);
+ return true;
+}
- uint32_t old_snd_nxt = state->snd_nxt;
+void TcpConnection::sendKeepAliveProbe()
+{
+ // Linux-style keepalive probe (net/ipv4/tcp_output.c tcp_xmit_probe_skb):
+ // a zero-length segment carrying seq = snd_una - 1. That sequence number is
+ // outside the receiver's window, so the peer answers with a plain ACK
+ // without accepting any data. Unlike sendProbe(), no real byte is sent and
+ // snd_nxt/snd_max are not advanced.
+ const auto& tcpHeader = makeShared();
- EV_INFO << "Sending 1 byte as probe, with seq=" << state->snd_nxt << "\n";
- sendSegment(1);
+ tcpHeader->setAckBit(true);
+ tcpHeader->setSequenceNo(state->snd_una - 1);
+ tcpHeader->setAckNo(state->rcv_nxt);
+ tcpHeader->setWindow(updateRcvWnd());
- // remember highest seq sent (snd_nxt may be set back on retransmission,
- // but we'll need snd_max to check validity of ACKs -- they must ack
- // something we really sent)
- state->snd_max = state->snd_nxt;
+ writeHeaderOptions(tcpHeader);
+ Packet *fp = new Packet("TcpKeepAlive");
- emit(unackedSignal, state->snd_max - state->snd_una);
+ EV_INFO << "Sending keepalive probe, seq=" << (state->snd_una - 1) << "\n";
- // notify
- tcpAlgorithm->ackSent();
- tcpAlgorithm->dataSent(old_snd_nxt);
+ // pure control packet: must be sent with the not-ECT codepoint
+ state->sndAck = true;
+ sendToIP(fp, tcpHeader);
+ state->sndAck = false;
+}
- return true;
+void TcpConnection::markOutstandingLostOnRto()
+{
+ if (!state->sack_enabled || rexmitQueue == nullptr || rexmitQueue->getQueueLength() == 0)
+ return;
+ // Clamp to the scoreboard's range: snd_una may sit below the queue start (already
+ // discarded) and snd_max may sit above the queue end (e.g. an outstanding FIN,
+ // which carries no data byte in the rexmit queue).
+ uint32_t from = state->snd_una;
+ uint32_t to = state->snd_max;
+ if (seqLess(from, rexmitQueue->getBufferStartSeq()))
+ from = rexmitQueue->getBufferStartSeq();
+ if (seqGreater(to, rexmitQueue->getBufferEndSeq()))
+ to = rexmitQueue->getBufferEndSeq();
+ if (seqLess(from, to))
+ rexmitQueue->markLost(from, to);
}
void TcpConnection::retransmitOneSegment(bool called_at_rto)
{
- // rfc-3168, page 20:
- // ECN-capable TCP implementations MUST NOT set either ECT codepoint
- // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets
+ // RFC 3168, page 20
+ // "ECN-capable TCP implementations MUST NOT set either ECT codepoint
+ // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets"
if (state && state->ect)
state->rexmit = true;
@@ -1179,8 +2386,16 @@ void TcpConnection::retransmitOneSegment(bool called_at_rto)
// retransmit one segment at snd_una, and set snd_nxt accordingly (if not called at RTO)
state->snd_nxt = state->snd_una;
+ // The SYN-ACK's sequence slot is not data: a TCP Fast Open server can be
+ // retransmitting response data from SYN_RCVD while the SYN-ACK itself is
+ // still unacknowledged (snd_una == iss; the SYN-REXMIT timer owns that
+ // slot). Data retransmission starts at the first data byte, or the send
+ // and rexmit queues (which begin at iss+1) would be walked out of range.
+ if (fsm.getState() == TCP_S_SYN_RCVD && seqLess(state->snd_nxt, state->iss + 1))
+ state->snd_nxt = state->iss + 1;
+
// When FIN sent the snd_max - snd_nxt larger than bytes available in queue
- uint32_t bytes = std::min(std::min(state->snd_mss, state->snd_max - state->snd_nxt),
+ uint32_t bytes = std::min(std::min(state->snd_effmss, state->snd_max - state->snd_nxt),
sendQueue->getBytesAvailable(state->snd_nxt));
// FIN (without user data) needs to be resent
@@ -1191,14 +2406,16 @@ void TcpConnection::retransmitOneSegment(bool called_at_rto)
sendFin();
tcpAlgorithm->segmentRetransmitted(state->snd_nxt, state->snd_nxt + 1);
state->snd_max = ++state->snd_nxt;
+ emit(sndMaxSignal, state->snd_max);
emit(unackedSignal, state->snd_max - state->snd_una);
}
else {
ASSERT(bytes != 0);
+ uint32_t rexmitStart = state->snd_nxt; // == snd_una except for the SYN_RCVD clamp above
sendSegment(bytes);
- tcpAlgorithm->segmentRetransmitted(state->snd_una, state->snd_nxt);
+ tcpAlgorithm->segmentRetransmitted(rexmitStart, state->snd_nxt);
if (!called_at_rto) {
if (seqGreater(old_snd_nxt, state->snd_nxt))
@@ -1209,10 +2426,11 @@ void TcpConnection::retransmitOneSegment(bool called_at_rto)
tcpAlgorithm->ackSent();
if (state->sack_enabled) {
- // RFC 3517, page 7: "(3) Retransmit the first data segment presumed dropped -- the segment
+ // RFC 6675, page 8: "(4.3) Retransmit the first data segment presumed dropped -- the segment
// starting with sequence number HighACK + 1. To prevent repeated
- // retransmission of the same data, set HighRxt to the highest
- // sequence number in the retransmitted segment."
+ // retransmission of the same data or a premature rescue retransmission,
+ // set both HighRxt and RescueRxt to the highest sequence number in
+ // the retransmitted segment."
state->highRxt = rexmitQueue->getHighestRexmittedSeqNum();
}
}
@@ -1221,17 +2439,91 @@ void TcpConnection::retransmitOneSegment(bool called_at_rto)
state->rexmit = false;
}
+bool TcpConnection::sendTlpProbe()
+{
+ // Prefer probing with NEW data (Linux tcp_send_loss_probe tries the send head
+ // first): it advances the receiver's state and can be acked normally.
+ uint32_t available = sendQueue->getBytesAvailable(state->snd_max);
+ if (available > 0 && seqLess(state->snd_max, state->snd_una + state->snd_wnd)) {
+ uint32_t win = state->snd_una + state->snd_wnd - state->snd_max;
+ uint32_t bytes = std::min(std::min(state->snd_mss, available), win);
+ if (bytes > 0) {
+ uint32_t old_snd_nxt = state->snd_nxt;
+ state->snd_nxt = state->snd_max;
+ uint32_t sent = sendSegment(bytes);
+ if (seqGreater(old_snd_nxt, state->snd_nxt))
+ state->snd_nxt = old_snd_nxt;
+ if (sent > 0) {
+ state->tlpRetrans = false;
+ EV_INFO << "TLP: probing with " << sent << " bytes of new data\n";
+ return true;
+ }
+ }
+ }
+
+ // A FIN that has already been sent occupies the highest-sequence "segment":
+ // probe by resending the FIN (Linux retransmits the tail skb, and the tail
+ // skb is the FIN-only skb then), not the last MSS of data below it.
+ if (state->send_fin && state->snd_fin_seq == sendQueue->getBufferEndSeq()
+ && state->snd_max == state->snd_fin_seq + 1) {
+ state->snd_nxt = state->snd_fin_seq;
+ sendFin();
+ tcpAlgorithm->segmentRetransmitted(state->snd_fin_seq, state->snd_fin_seq + 1);
+ state->snd_nxt = state->snd_fin_seq + 1;
+ state->tlpRetrans = true;
+ EV_INFO << "TLP: probing by resending the FIN\n";
+ return true;
+ }
+
+ // No new data: retransmit the last (highest-sequence) outstanding segment
+ // (Linux retransmits the tail skb, fragmenting off its last MSS). Bound the
+ // region by the send queue's buffered-data start, NOT snd_una: when the SYN
+ // is still unacked (e.g. a TFO server whose SYN-ACK+data was never acked)
+ // snd_una points at the SYN, which is not in the data-only send queue, so
+ // start = snd_max - (snd_max - snd_una) would fall below the buffer and abort
+ // createSegmentWithBytes(). The SYN is the RTO/SYN-retransmit path's job.
+ uint32_t bufStart = sendQueue->getBufferStartSeq();
+ if (seqGE(bufStart, state->snd_max))
+ return false; // nothing buffered to retransmit
+ uint32_t outstanding = state->snd_max - bufStart;
+ uint32_t len = std::min(state->snd_mss, outstanding);
+ uint32_t start = state->snd_max - len;
+
+ // RFC 3168: no ECT on retransmissions (classic-ECN rule; see retransmitOneSegment)
+ if (state->ect)
+ state->rexmit = true;
+ uint32_t old_snd_nxt = state->snd_nxt;
+ state->snd_nxt = start;
+ uint32_t sent = sendSegment(len);
+ tcpAlgorithm->segmentRetransmitted(start, start + sent);
+ if (seqGreater(old_snd_nxt, state->snd_nxt))
+ state->snd_nxt = old_snd_nxt;
+ if (state->ect)
+ state->rexmit = false;
+ if (sent == 0)
+ return false;
+ state->tlpRetrans = true;
+ EV_INFO << "TLP: probing by retransmitting the last " << sent << " bytes\n";
+ return true;
+}
+
void TcpConnection::retransmitData()
{
- // rfc-3168, page 20:
- // ECN-capable TCP implementations MUST NOT set either ECT codepoint
- // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets
+ // RFC 3168, page 20
+ // "ECN-capable TCP implementations MUST NOT set either ECT codepoint
+ // (ECT(0) or ECT(1)) in the IP header for retransmitted data packets"
if (state && state->ect)
state->rexmit = true;
// retransmit everything from snd_una
state->snd_nxt = state->snd_una;
+ // ... except the unacked SYN-ACK's sequence slot, which is not in the
+ // send queue (TCP Fast Open server data in SYN_RCVD; see
+ // retransmitOneSegment's matching clamp)
+ if (fsm.getState() == TCP_S_SYN_RCVD && seqLess(state->snd_nxt, state->iss + 1))
+ state->snd_nxt = state->iss + 1;
+
uint32_t bytesToSend = state->snd_max - state->snd_nxt;
// FIN (without user data) needs to be resent
@@ -1241,6 +2533,7 @@ void TcpConnection::retransmitData()
state->snd_nxt = state->snd_max;
sendFin();
state->snd_max = ++state->snd_nxt;
+ emit(sndMaxSignal, state->snd_max);
emit(unackedSignal, state->snd_max - state->snd_una);
return;
@@ -1250,7 +2543,7 @@ void TcpConnection::retransmitData()
// TODO - avoid to send more than allowed - check cwnd and rwnd before retransmitting data!
while (bytesToSend > 0) {
- uint32_t bytes = std::min(bytesToSend, state->snd_mss);
+ uint32_t bytes = std::min(bytesToSend, state->snd_effmss);
bytes = std::min(bytes, sendQueue->getBytesAvailable(state->snd_nxt));
uint32_t sentBytes = sendSegment(bytes);
@@ -1272,6 +2565,14 @@ void TcpConnection::readHeaderOptions(const Ptr& tcpHeader)
{
EV_INFO << "Tcp Header Option(s) received:\n";
+ // AccECN TCP option: reset per-segment before scanning this segment's options,
+ // so a segment that doesn't carry the option never lets processAckInEstabEtc() reuse
+ // a delta computed from some earlier segment.
+ state->accEcnOptionCebDeltaValid = false;
+ state->accEcnOptionE0DeltaValid = false;
+ state->accEcnOptionE1DeltaValid = false;
+ state->accEcnTsProgress = false;
+
for (uint i = 0; i < tcpHeader->getHeaderOptionArraySize(); i++) {
const TcpOption *option = tcpHeader->getHeaderOption(i);
short kind = option->getKind();
@@ -1301,14 +2602,100 @@ void TcpConnection::readHeaderOptions(const Ptr& tcpHeader)
ok = processSACKPermittedOption(tcpHeader, *check_and_cast(option));
break;
- case TCPOPTION_SACK: // SACK=5
- ok = processSACKOption(tcpHeader, *check_and_cast(option));
+ case TCPOPTION_SACK: { // SACK=5
+ // A SACK block from a peer we never negotiated SACK with, or one that
+ // arrives before the algorithm has a SACK-capable recovery object, is
+ // malformed input -- drop the option, never abort the simulation.
+ auto *recovery = state->sack_enabled ? dynamic_cast(tcpAlgorithm->getRecovery()) : nullptr;
+ if (recovery == nullptr) {
+ EV_ERROR << "ERROR: " << (state->sack_enabled ? "no SACK-capable recovery in use" : "SACK received but sack_enabled is false") << ", dropping SACK option\n";
+ ok = false;
+ }
+ else
+ ok = recovery->processSACKOption(tcpHeader, *check_and_cast(option));
break;
+ }
case TCPOPTION_TIMESTAMP: // TS=8
ok = processTSOption(tcpHeader, *check_and_cast(option));
break;
+ case TCPOPTION_TCP_FASTOPEN: // TFO=34
+ ok = processFastOpenOption(tcpHeader, *check_and_cast(option));
+ break;
+
+ case TCPOPTION_RFC3692_STYLE_EXPERIMENT_2: // kind 254: only the pre-standardization
+ // TCP Fast Open experimental sub-type (0xF989 magic) is understood, and only
+ // when fastopenExpOptionEnabled opts into accepting it. Any other kind-254 use,
+ // or this same sub-type while the gate is off, is a dynamic_cast miss / early
+ // return here -- silently ignored, same as an ordinary TcpOptionUnknown kind
+ // elsewhere in this switch gets no special handling either.
+ if (state->fastopenExpOptionEnabled) {
+ if (auto *expOption = dynamic_cast(option))
+ ok = processFastOpenExpOption(tcpHeader, *expOption);
+ }
+ break;
+
+ case TCPOPTION_ACCECN0: // draft-ietf-tcpm-accurate-ecn, E0B/CEB/E1B byte counters
+ case TCPOPTION_ACCECN1: { // same option, E1B/CEB/E0B field order
+ // G7: decode the peer's report of bytes IT received from US (opposite
+ // direction from rcvEct*Bytes/rcvCeBytes, which are what we observed on
+ // bytes the peer sent us). The serializer already resolved the
+ // kind-dependent wire order into these semantic accessors.
+ if (length != 2 && length != 5 && length != 8 && length != 11) {
+ EV_ERROR << "ERROR: AccECN option length incorrect\n";
+ ok = false;
+ break;
+ }
+ auto *aeOpt = check_and_cast(option);
+ state->sawAccEcnOpt = true; // Linux tp->saw_accecn_opt
+ // A variable-length AccECN option (2/5/8/11) may omit trailing
+ // counters; absent fields are 0. CEB is the 2nd field in both wire
+ // orders, so a usable CE-byte delta is present iff length >= 8.
+ // Decode the ECT0/ECT1 byte counters into raw (offset-corrected) values
+ // and flag them valid, exactly as the CEB below. The first present counter
+ // (E0B for ACCECN0, E1B for ACCECN1) appears at length >= 5; the third
+ // counter at length >= 11. The delta against the baseline and the baseline
+ // advance both happen later in processAckInEstabEtc()'s ACE block, so an
+ // early-return ACK never advances a baseline while dropping its delta.
+ if (length >= 5) {
+ if (kind == TCPOPTION_ACCECN0) {
+ state->accEcnOptionRawE0Bytes = aeOpt->getEct0Bytes() - 1;
+ state->accEcnOptionE0DeltaValid = true;
+ }
+ else {
+ state->accEcnOptionRawE1Bytes = aeOpt->getEct1Bytes() - 1;
+ state->accEcnOptionE1DeltaValid = true;
+ }
+ }
+ if (length >= 11) {
+ if (kind == TCPOPTION_ACCECN0) {
+ state->accEcnOptionRawE1Bytes = aeOpt->getEct1Bytes() - 1;
+ state->accEcnOptionE1DeltaValid = true;
+ }
+ else {
+ state->accEcnOptionRawE0Bytes = aeOpt->getEct0Bytes() - 1;
+ state->accEcnOptionE0DeltaValid = true;
+ }
+ }
+ if (length < 8) {
+ EV_INFO << "Tcp Header Option AccECN(kind=" << kind << ", len=" << length << ", no CEB) received\n";
+ break;
+ }
+ // CEB itself is stored raw (offset-corrected only) here, NOT diffed against
+ // peerReportedCeBytes yet -- the delta computation and the baseline advance
+ // both happen together in processAckInEstabEtc()'s ACE block, the one place
+ // that actually consumes it, so an early-return path there (e.g. an ACK
+ // beyond snd_max) can never advance the baseline without folding the delta
+ // into deliveredCeBytes. See the state field's own comment for why.
+ state->accEcnOptionRawCeBytes = aeOpt->getCeBytes();
+ state->accEcnOptionCebDeltaValid = true;
+ EV_INFO << "Tcp Header Option AccECN(kind=" << kind << ", E0B=" << aeOpt->getEct0Bytes()
+ << ", E1B=" << aeOpt->getEct1Bytes() << ", CEB=" << state->accEcnOptionRawCeBytes
+ << ") received\n";
+ break;
+ }
+
// TODO add new TCPOptions here once they are implemented
// TODO delegate to TcpAlgorithm as well -- it may want to recognized additional options
@@ -1332,27 +2719,35 @@ bool TcpConnection::processMSSOption(const Ptr& tcpHeader, cons
return false;
}
- // RFC 2581, page 1:
+ // RFC 5681, page 3:
// "The SMSS is the size of the largest segment that the sender can transmit.
// This value can be based on the maximum transmission unit of the network,
- // the path MTU discovery [MD90] algorithm, RMSS (see next item), or other
+ // the path MTU discovery [RFC1191, RFC4821] algorithm, RMSS (see next item), or other
// factors. The size does not include the TCP/IP headers and options."
//
// "The RMSS is the size of the largest segment the receiver is willing to accept.
// This is the value specified in the MSS option sent by the receiver during
- // connection startup. Or, if the MSS option is not used, 536 bytes [Bra89].
+ // connection startup. Or, if the MSS option is not used, it is 536 bytes [RFC1122].
// The size does not include the TCP/IP headers and options."
//
//
// The value of snd_mss (SMSS) is set to the minimum of snd_mss (local parameter) and
// the value specified in the MSS option received during connection startup.
+ state->peerAdvertisedMss = option.getMaxSegmentSize(); // raw, pre-clamp (TFO metrics cache)
state->snd_mss = std::min(state->snd_mss, (uint32_t)option.getMaxSegmentSize());
if (state->snd_mss == 0)
- state->snd_mss = 536;
+ // RFC 9293, section 3.7.1
+ //"
+ // If an MSS Option is not received at connection setup,
+ // TCP implementations MUST assume a default send MSS of
+ // 536 (576 - 40) for IPv4 or 1220 (1280 - 60) for IPv6 (MUST-15).
+ //"
+ state->snd_mss = remoteAddr.getType() == L3Address::IPv4 ? 536 : 1220;
// Store negotiated MSS for PMTUD: this is the value we restore after the probe timeout
state->pmtudOriginalMss = state->snd_mss;
+ state->snd_effmss = calculateEffectiveMss();
EV_INFO << "Tcp Header Option MSS(=" << option.getMaxSegmentSize() << ") received, SMSS is set to " << state->snd_mss << "\n";
return true;
@@ -1375,7 +2770,7 @@ bool TcpConnection::processWSOption(const Ptr& tcpHeader, const
state->snd_wnd_scale = option.getWindowScale();
EV_INFO << "Tcp Header Option WS(=" << state->snd_wnd_scale << ") received, WS (ws_enabled) is set to " << state->ws_enabled << "\n";
- if (state->snd_wnd_scale > 14) { // RFC 1323, page 11: "the shift count must be limited to 14"
+ if (state->snd_wnd_scale > 14) { // RFC 7323, page 10: "the shift count must be limited to 14"
EV_ERROR << "ERROR: Tcp Header Option WS received but shift count value is exceeding 14\n";
state->snd_wnd_scale = 14;
}
@@ -1385,6 +2780,11 @@ bool TcpConnection::processWSOption(const Ptr& tcpHeader, const
bool TcpConnection::processTSOption(const Ptr& tcpHeader, const TcpOptionTimestamp& option)
{
+ // Eifel input (RFC 3522 / Linux rx_opt.rcv_tsecr): remember the echo so the
+ // undo logic can compare it against the first retransmission's timestamp.
+ if (tcpHeader->getAckBit() && option.getEchoedTimestamp() != 0)
+ state->lastRcvdTSecr = option.getEchoedTimestamp();
+
if (option.getLength() != 10) {
EV_ERROR << "ERROR: length incorrect\n";
return false;
@@ -1406,16 +2806,32 @@ bool TcpConnection::processTSOption(const Ptr& tcpHeader, const
else
EV_INFO << "Tcp Header Option TS(TSval=" << option.getSenderTimestamp() << ", TSecr=" << option.getEchoedTimestamp() << ") received\n";
- // RFC 1323, page 35:
- // "Check whether the segment contains a Timestamps option and bit
- // Snd.TS.OK is on. If so:
- // If SEG.TSval < TS.Recent, then test whether connection has
- // been idle less than 24 days; if both are true, then the
- // segment is not acceptable; follow steps below for an
- // unacceptable segment.
- // If SEG.SEQ is equal to Last.ACK.sent, then save SEG.[TSval] in
- // variable TS.Recent."
- if (state->ts_enabled) {
+ // RFC 7323, page 42:
+ // "Check whether the segment contains a Timestamps option and
+ // if bit Snd.TS.OK is on. If so:
+ //
+ // If SEG.TSval < TS.Recent and the RST bit is off:
+ //
+ // If the connection has been idle more than 24 days,
+ // save SEG.TSval in variable TS.Recent, else the segment
+ // is not acceptable; follow the steps below for an
+ // unacceptable segment.
+ //
+ // If SEG.TSval >= TS.Recent and SEG.SEQ <= Last.ACK.sent,
+ // then save SEG.TSval in variable TS.Recent."
+ if (tcpHeader->getSynBit() && state->ts_support) {
+ // Handshake segment (SYN or SYN-ACK): its TSval initializes TS.Recent
+ // unconditionally (RFC 7323 section 4.3's last-ACK-sent bookkeeping
+ // cannot accept it -- no ACK has ever been sent yet; and on the
+ // passive side ts_enabled itself only becomes true once the SYN-ACK
+ // goes out). This is what makes the passive side echo the SYN's TSval
+ // in its SYN-ACK, and the active side echo the SYN-ACK's TSval in the
+ // handshake-completing ACK, as Linux does (tcp_store_ts_recent in
+ // both handshake paths).
+ state->ts_recent = option.getSenderTimestamp();
+ EV_DETAIL << "Initializing ts_recent from handshake segment: ts_recent=" << state->ts_recent << "\n";
+ }
+ else if (state->ts_enabled) {
if (seqLess(option.getSenderTimestamp(), state->ts_recent)) {
if ((simTime() - state->time_last_data_sent) > PAWS_IDLE_TIME_THRESH) { // PAWS_IDLE_TIME_THRESH = 24 days
EV_DETAIL << "PAWS: Segment is not acceptable, TSval=" << option.getSenderTimestamp() << " in " << stateName(fsm.getState()) << " state received: dropping segment\n";
@@ -1423,14 +2839,127 @@ bool TcpConnection::processTSOption(const Ptr& tcpHeader, const
}
}
else if (seqLE(tcpHeader->getSequenceNo(), state->last_ack_sent)) { // Note: test is modified according to the latest proposal of the tcplw@cray.com list (Braden 1993/04/26)
- state->ts_recent = option.getSenderTimestamp();
- EV_DETAIL << "Updating ts_recent from segment: new ts_recent=" << state->ts_recent << "\n";
+ // ... but never from a segment whose ACK is invalid (acks data we
+ // never sent): options are processed before ACK validation here,
+ // and accepting such a segment's (possibly wild) TSval would arm
+ // PAWS against every subsequent legitimate segment. Linux only
+ // stores ts_recent after the incoming segment passes validation.
+ if (tcpHeader->getAckBit() && seqGreater(tcpHeader->getAckNo(), state->snd_max))
+ EV_DETAIL << "Not updating ts_recent: segment acks unsent data\n";
+ else {
+ // positive delta = Linux FLAG_TS_PROGRESS (consumed by the AccECN
+ // ACE decode's forward-progress test this segment)
+ if (seqGreater(option.getSenderTimestamp(), state->ts_recent))
+ state->accEcnTsProgress = true;
+ state->ts_recent = option.getSenderTimestamp();
+ EV_DETAIL << "Updating ts_recent from segment: new ts_recent=" << state->ts_recent << "\n";
+ }
}
}
return true;
}
+bool TcpConnection::processFastOpenCookieBytes(const std::vector& cookie)
+{
+ // RFC 7413 SS4.1: server processing an incoming SYN, or client processing a SYN-ACK.
+ // Shared by both the standard (kind 34, processFastOpenOption()) and legacy
+ // experimental (kind 254 + 0xF989 magic, processFastOpenExpOption()) options --
+ // once the cookie bytes are extracted, the two forms are handled identically.
+ bool isServerSyn = state->fastopenServerEnabled && fsm.getState() == TCP_S_LISTEN;
+ bool isClientSynAck = state->fastopenClientEnabled && fsm.getState() == TCP_S_SYN_SENT;
+ if (!isServerSyn && !isClientSynAck)
+ return true; // Fast Open not applicable in this role/state -- accepted, no-op.
+
+ unsigned int cookieLen = cookie.size();
+
+ if (isServerSyn) {
+ if (cookieLen == 0) {
+ // Empty cookie: peer is requesting one for a future connection attempt.
+ state->fastopenCookieRequested = true;
+ state->fastopenCookieToSend = tcpMain->generateFastOpenCookie(localAddr, remoteAddr, state->fastopenCookieBytes);
+ state->fastopenSendCookieOption = true;
+ EV_INFO << "Fast Open: cookie requested, generated a fresh one to echo\n";
+ }
+ else {
+ std::vector want = tcpMain->generateFastOpenCookie(localAddr, remoteAddr, cookieLen);
+ if (state->fastopenLenientCookieValidation || cookie == want) {
+ state->fastopenCookieValid = true;
+ EV_INFO << "Fast Open: cookie accepted (" << (state->fastopenLenientCookieValidation ? "lenient" : "verified") << ")\n";
+ }
+ else {
+ // Mismatch under strict validation: refresh, matching RFC 7413's
+ // "always give the client a fresh cookie on failure" guidance.
+ state->fastopenCookieToSend = tcpMain->generateFastOpenCookie(localAddr, remoteAddr, state->fastopenCookieBytes);
+ state->fastopenSendCookieOption = true;
+ EV_INFO << "Fast Open: cookie mismatch under strict validation, offering a fresh one\n";
+ }
+ }
+ }
+ else { // isClientSynAck
+ if (!state->fastopenSynCarriedOption) {
+ // Linux tcp_rcv_fastopen_synack: "Ignore an unsolicited cookie" --
+ // our SYN carried no TFO option (cookie-less mode, or no TFO at
+ // all), so a cookie the server volunteered is NOT cached
+ // (cookie-less-sendto pins the later cookie-mode connect still
+ // sending an empty cookie REQUEST).
+ EV_INFO << "Fast Open: ignoring unsolicited " << cookieLen << "-byte cookie (our SYN carried no TFO option)\n";
+ }
+ else if (cookieLen >= 4 && cookieLen <= 16) {
+ // remember the peer's ANNOUNCED MSS with the cookie (Linux caches
+ // both in tcp_metrics): the NEXT connect's SYN-payload cap is
+ // cachedMss - 40, before any live MSS negotiation has happened.
+ // The raw option value, NOT snd_mss -- a local TCP_MAXSEG clamp on
+ // THIS connection must not shrink the cache (Linux reparses the
+ // SYN-ACK to bypass the user clamp; syn-data-mss pins a 1300-byte
+ // next-SYN payload from a cached 1340 despite this connection's
+ // TCP_MAXSEG 1040).
+ uint32_t cacheMss = state->peerAdvertisedMss > 0 ? state->peerAdvertisedMss : state->snd_mss;
+ tcpMain->setFastOpenCookie(remoteAddr, cookie, cacheMss);
+ // Remember WHICH option form carried it, so the next connection echoes the
+ // cookie the same way (Linux keeps foc->exp beside the cookie in
+ // tcp_metrics). The entry must exist, so this must follow setFastOpenCookie().
+ tcpMain->setFastOpenCookieExpForm(remoteAddr, state->fastopenPeerUsedExpOption);
+ EV_INFO << "Fast Open: learned a " << cookieLen << "-byte cookie for " << remoteAddr.str()
+ << " (kind " << (state->fastopenPeerUsedExpOption ? 254 : 34) << ")\n";
+ }
+ else if (cookieLen > 0) {
+ // RFC 7413 SS4.1.2: valid cookies are 4-16 bytes (Linux
+ // TCP_FASTOPEN_COOKIE_MIN/MAX in tcp_parse_fastopen_option()).
+ // An out-of-range cookie must NOT be cached.
+ EV_WARN << "Fast Open: ignoring out-of-range " << cookieLen << "-byte cookie from " << remoteAddr.str() << "\n";
+ }
+ }
+ return true;
+}
+
+bool TcpConnection::processFastOpenOption(const Ptr& tcpHeader, const TcpOptionTcpFastOpen& option)
+{
+ unsigned int cookieLen = option.getCookieArraySize();
+ std::vector cookie(cookieLen);
+ for (unsigned int i = 0; i < cookieLen; i++)
+ cookie[i] = option.getCookie(i);
+ return processFastOpenCookieBytes(cookie);
+}
+
+bool TcpConnection::processFastOpenExpOption(const Ptr& tcpHeader, const TcpOptionTcpFastOpenExp& option)
+{
+ // Pre-standardization form (RFC 7413 Appendix A): same semantics as kind 34,
+ // gated separately (fastopenExpOptionEnabled) since accepting it is a distinct
+ // opt-in -- readHeaderOptions() only calls this when that gate is on.
+ unsigned int cookieLen = option.getCookieArraySize();
+ std::vector cookie(cookieLen);
+ for (unsigned int i = 0; i < cookieLen; i++)
+ cookie[i] = option.getCookie(i);
+ // Linux echoes the cookie in the same option form the client used
+ // (foc->exp propagates request->response); remember it for the SYN-ACK.
+ // On the CLIENT this is equally the record of how the server answered, which
+ // decides both how the learned cookie is cached and how it is echoed later --
+ // so set it in either role, not just LISTEN.
+ state->fastopenPeerUsedExpOption = true;
+ return processFastOpenCookieBytes(cookie);
+}
+
bool TcpConnection::processSACKPermittedOption(const Ptr& tcpHeader, const TcpOptionSackPermitted& option)
{
if (option.getLength() != 2) {
@@ -1449,37 +2978,200 @@ bool TcpConnection::processSACKPermittedOption(const Ptr& tcpHe
return true;
}
+uint32_t TcpConnection::calculateEffectiveMss()
+{
+ // calculate mss minus TCP options length for cwnd calculations
+ // TCP options used during the handshake is ignored
+ // only TCP options used in established connections are considered
+ // we only support two such options: timestamp and sack options
+ // we only calculate with the timestamp option
+ // the sack option is ignored because it is variable width and
+ // the number of sack blocks is not yet known when this value is needed
+ // also it is not important during recovery and during bidirectional traffic
+ return state->snd_mss - (state->ts_enabled ? 10 + 1 + 1 : 0); // timestamp option + end of options + padding
+}
+
TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
{
- // SYN flag set and connetion in INIT or LISTEN state (or after synRexmit timeout)
+ // SYN flag set and the connection state is INIT or LISTEN state (or after synRexmit
+ // timeout, or sending a TCP Fast Open deferred SYN for the first time --
+ // fastopenSynDeferred stays true through sendSyn() itself for exactly this purpose,
+ // see process_SEND)
+ if (state->advertisedMss == (uint32_t)-1)
+ state->advertisedMss = (remoteAddr.getType() == L3Address::IPv6) ? 1440 : 1460;
+ // Resolve the address-family-derived default MSS (mss = -1 sentinel) now that
+ // the remote address is known and before we advertise/use it. IPv6 has 40 bytes
+ // of base header vs IPv4's 20, so a 1500-byte MTU yields 1440 vs 1460.
+ if (state->snd_mss == (uint32_t)-1) {
+ state->snd_mss = (remoteAddr.getType() == L3Address::IPv6) ? 1440 : 1460;
+ state->snd_effmss = calculateEffectiveMss();
+ EV_DETAIL << "Derived default MSS from address family: snd_mss=" << state->snd_mss << "\n";
+ }
+
if (tcpHeader->getSynBit() && (fsm.getState() == TCP_S_INIT || fsm.getState() == TCP_S_LISTEN
|| ((fsm.getState() == TCP_S_SYN_SENT || fsm.getState() == TCP_S_SYN_RCVD)
- && state->syn_rexmit_count > 0)))
+ && (state->syn_rexmit_count > 0 || state->fastopenSynDeferred
+ // simultaneous open: the crossing-SYN reply (a first
+ // SYN-ACK sent while still in SYN_SENT) carries the
+ // full handshake option set, same as any other
+ // handshake segment -- without this it fell into the
+ // established-states branch and went out bare
+ || tcpHeader->getAckBit()))))
{
- // MSS header option
- if (state->snd_mss > 0) {
+ // MSS header option: announces OUR receive limit (advertisedMss), not
+ // snd_mss -- by SYN-ACK time snd_mss is already clamped to the peer's
+ // announced MSS, and echoing that back is wrong (RFC 793/9293: each
+ // side announces its own limit; Linux advertises its own 1460 in the
+ // SYN-ACK regardless of the client's smaller MSS).
+ if (tcpMain->sendMssOption && state->advertisedMss > 0) {
TcpOptionMaxSegmentSize *option = new TcpOptionMaxSegmentSize();
- option->setMaxSegmentSize(state->snd_mss);
+ option->setMaxSegmentSize(state->advertisedMss);
tcpHeader->appendHeaderOption(option);
- EV_INFO << "Tcp Header Option MSS(=" << state->snd_mss << ") sent\n";
+ EV_INFO << "Tcp Header Option MSS(=" << state->advertisedMss << ") sent\n";
+ }
+
+ // TS header option
+ if (state->ts_support && (state->rcv_initial_ts || (fsm.getState() == TCP_S_INIT
+ || (fsm.getState() == TCP_S_SYN_SENT && (state->syn_rexmit_count > 0 || state->fastopenSynDeferred)))))
+ {
+ if (tcpMain->alignOptions && !state->sack_support) { // if SACK is supported by host, do not add NOPs to this segment
+ // 2 padding bytes
+ tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
+ tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
+ }
+
+ TcpOptionTimestamp *option = new TcpOptionTimestamp();
+
+ // Update TS variables
+ // RFC 7323, page 12: "The TSval field contains the current value of the timestamp clock of the Tcp sending the option."
+ option->setSenderTimestamp(convertSimtimeToTS(simTime()));
+
+ // RFC 7323, page 17: "(3) When a TSopt is sent, its TSecr field is set to the current TS.Recent value."
+ // RFC 7323, page 12:
+ // "The TSecr field is valid if the ACK bit is set in the TCP header. If
+ // the ACK bit is not set in the outgoing TCP header, the sender of that
+ // segment SHOULD set the TSecr field to zero. When the ACK bit is set
+ // in an outgoing segment, the sender MUST echo a recently received
+ // TSval sent by the remote TCP in the TSval field of a Timestamps
+ // option."
+ option->setEchoedTimestamp(tcpHeader->getAckBit() ? state->ts_recent : 0);
+
+ state->snd_initial_ts = true;
+ state->ts_enabled = state->ts_support && state->snd_initial_ts && state->rcv_initial_ts;
+ EV_INFO << "Tcp Header Option TS(TSval=" << option->getSenderTimestamp() << ", TSecr=" << option->getEchoedTimestamp() << ") sent, TS (ts_enabled) is set to " << state->ts_enabled << "\n";
+ tcpHeader->appendHeaderOption(option);
+ }
+
+ // TCP Fast Open (RFC 7413) cookie option, server side: echo a (possibly
+ // fresh) cookie in the SYN-ACK. 2 trailing NOPs pad the 2- or 10-byte
+ // (with the default 8-byte cookie) option to a 4-byte-aligned option area --
+ // unlike MSS/WS/SACK_PERMITTED/TS, no other option's NOPs can double up here
+ // since TFO is server-to-client-only at this point in the plan.
+ if (state->fastopenServerEnabled && state->fastopenSendCookieOption) {
+ if (state->fastopenPeerUsedExpOption) {
+ // Echo in the experimental form the client used (RFC 7413
+ // Appendix A: kind 254 + 0xF989 magic), as Linux does
+ // (foc->exp propagates request->response). The 12-byte option
+ // (4 base + 8 cookie) is 4-byte-aligned on its own, so no NOP
+ // padding.
+ TcpOptionTcpFastOpenExp *option = new TcpOptionTcpFastOpenExp();
+ option->setExpId(0xF989);
+ option->setCookieArraySize(state->fastopenCookieToSend.size());
+ for (size_t i = 0; i < state->fastopenCookieToSend.size(); i++)
+ option->setCookie(i, state->fastopenCookieToSend[i]);
+ option->setLength(4 + state->fastopenCookieToSend.size());
+ tcpHeader->appendHeaderOption(option);
+ }
+ else {
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ TcpOptionTcpFastOpen *fastOpenOption = new TcpOptionTcpFastOpen();
+ fastOpenOption->setCookieArraySize(state->fastopenCookieToSend.size());
+ for (size_t i = 0; i < state->fastopenCookieToSend.size(); i++)
+ fastOpenOption->setCookie(i, state->fastopenCookieToSend[i]);
+ fastOpenOption->setLength(2 + state->fastopenCookieToSend.size());
+ tcpHeader->appendHeaderOption(fastOpenOption);
+ }
+ EV_INFO << "Tcp Header Option Fast Open cookie (" << state->fastopenCookieToSend.size()
+ << " bytes, kind " << (state->fastopenPeerUsedExpOption ? 254 : 34) << ") sent\n";
+ }
+
+ // TCP Fast Open (RFC 7413) cookie option, client side: echo the cached
+ // cookie (data-bearing SYN, process_SEND's deferred path) or an empty
+ // cookie (dataless SYN requesting one, process_OPEN_ACTIVE's immediate path).
+ // Gated on fastopenRequested (this connection's own connect() opted in), not
+ // just the module-wide fastopenClientEnabled param -- otherwise a plain
+ // connect() to a destination with a cookie cached from an earlier TFO
+ // connection would attach that cookie uninvited, which Linux's per-connection
+ // opt-in (MSG_FASTOPEN / TCP_FASTOPEN_CONNECT) never does.
+ // Linux drops the Fast Open option on SYN retransmits (RFC 7413
+ // section 4.1.3 / tcp_retransmit_skb clearing the fastopen request:
+ // a lost option-bearing SYN plausibly means a middlebox ate it), for
+ // both cookie requests and data-carrying SYNs.
+ // ... never on an ACK-bearing SYN (a simultaneous-open SYN-ACK carries
+ // no client cookie in Linux), and never in cookie-less client mode
+ // (tcp_fastopen bit 0x4: the SYN+data goes out with NO FO option even
+ // when a cookie happens to be cached -- tcp_fastopen_no_cookie()).
+ if (state->fastopenClientEnabled && state->fastopenRequested && state->syn_rexmit_count == 0
+ && !tcpHeader->getAckBit() && !tcpMain->par("fastopenClientNoCookieRequired").boolValue()) {
+ std::vector cachedCookie;
+ // fastopenCookieRequestPending is the authoritative "this connection is in
+ // cookie-REQUEST mode" signal set once, at connect() time, by
+ // process_OPEN_ACTIVE -- true both for a genuinely empty cache and for a
+ // cache hit overridden by isActiveFastOpenDisabled() (blackhole detection,
+ // F5.1): either way this SYN must look like "no cookie cached" (an empty
+ // request), not silently reveal a real cached cookie it chose not to use.
+ // Deliberately NOT re-checking isActiveFastOpenDisabled() here directly --
+ // that would also suppress an *already*-deferred connection's own SYN
+ // retransmissions if blackhole detection trips mid-flight (after this
+ // connection committed to using the cache), corrupting an in-flight
+ // data-bearing SYN into a data-bearing-but-cookie-less one.
+ bool haveCachedCookie = !state->fastopenCookieRequestPending && tcpMain->getFastOpenCookie(remoteAddr, cachedCookie);
+ if (haveCachedCookie || state->fastopenCookieRequestPending) {
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ if (tcpMain->getFastOpenUseExpOption(remoteAddr)) {
+ // RFC 7413 appendix A: kind 254 + 0xF989 magic. Used either because
+ // this destination answered in that form before, or because a kind-34
+ // request went unanswered and this is the retry.
+ TcpOptionTcpFastOpenExp *expOption = new TcpOptionTcpFastOpenExp();
+ expOption->setExpId(0xF989);
+ expOption->setCookieArraySize(cachedCookie.size());
+ for (size_t i = 0; i < cachedCookie.size(); i++)
+ expOption->setCookie(i, cachedCookie[i]);
+ expOption->setLength(4 + cachedCookie.size());
+ tcpHeader->appendHeaderOption(expOption);
+ }
+ else {
+ TcpOptionTcpFastOpen *clientOption = new TcpOptionTcpFastOpen();
+ clientOption->setCookieArraySize(cachedCookie.size());
+ for (size_t i = 0; i < cachedCookie.size(); i++)
+ clientOption->setCookie(i, cachedCookie[i]);
+ clientOption->setLength(2 + cachedCookie.size());
+ tcpHeader->appendHeaderOption(clientOption);
+ }
+ state->fastopenSynCarriedOption = true; // Linux tp->syn_fastopen
+ EV_INFO << "Tcp Header Option Fast Open cookie (" << cachedCookie.size() << " bytes, kind "
+ << (tcpMain->getFastOpenUseExpOption(remoteAddr) ? 254 : 34) << ") sent\n";
+ }
}
// WS header option
if (state->ws_support && (state->rcv_ws || (fsm.getState() == TCP_S_INIT
- || (fsm.getState() == TCP_S_SYN_SENT && state->syn_rexmit_count > 0))))
+ || (fsm.getState() == TCP_S_SYN_SENT && (state->syn_rexmit_count > 0 || state->fastopenSynDeferred)))))
{
- // 1 padding byte
- tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
+ if (tcpMain->alignOptions) // align
+ tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
// Update WS variables
if (state->ws_manual_scale > -1) {
state->rcv_wnd_scale = state->ws_manual_scale;
}
else {
- ulong scaled_rcv_wnd = receiveQueue->getFirstSeqNo() + state->maxRcvBuffer - state->rcv_nxt;
+ ulong scaled_rcv_wnd = state->maxRcvBuffer - receiveQueue->getAcknowledgedDataLength();
state->rcv_wnd_scale = 0;
- while (scaled_rcv_wnd > TCP_MAX_WIN && state->rcv_wnd_scale < 14) { // RFC 1323, page 11: "the shift count must be limited to 14"
+ while (scaled_rcv_wnd > TCP_MAX_WIN && state->rcv_wnd_scale < 14) { // RFC 7323, page 10: "the shift count must be limited to 14"
scaled_rcv_wnd = scaled_rcv_wnd >> 1;
state->rcv_wnd_scale++;
}
@@ -1495,9 +3187,9 @@ TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
// SACK_PERMITTED header option
if (state->sack_support && (state->rcv_sack_perm || (fsm.getState() == TCP_S_INIT
- || (fsm.getState() == TCP_S_SYN_SENT && state->syn_rexmit_count > 0))))
+ || (fsm.getState() == TCP_S_SYN_SENT && (state->syn_rexmit_count > 0 || state->fastopenSynDeferred)))))
{
- if (!state->ts_support) { // if TS is supported by host, do not add NOPs to this segment
+ if (tcpMain->alignOptions && !state->ts_support) { // if TS is supported by host, do not add NOPs to this segment
// 2 padding bytes
tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
@@ -1510,36 +3202,35 @@ TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
state->sack_enabled = state->sack_support && state->snd_sack_perm && state->rcv_sack_perm;
EV_INFO << "Tcp Header Option SACK_PERMITTED sent, SACK (sack_enabled) is set to " << state->sack_enabled << "\n";
}
-
- // TS header option
- if (state->ts_support && (state->rcv_initial_ts || (fsm.getState() == TCP_S_INIT
- || (fsm.getState() == TCP_S_SYN_SENT && state->syn_rexmit_count > 0))))
- {
- if (!state->sack_support) { // if SACK is supported by host, do not add NOPs to this segment
- // 2 padding bytes
- tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
- tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
- }
-
- TcpOptionTimestamp *option = new TcpOptionTimestamp();
-
- // Update TS variables
- // RFC 1323, page 13: "The Timestamp Value field (TSval) contains the current value of the timestamp clock of the Tcp sending the option."
- option->setSenderTimestamp(convertSimtimeToTS(simTime()));
-
- // RFC 1323, page 16: "(3) When a TSopt is sent, its TSecr field is set to the current TS.Recent value."
- // RFC 1323, page 13:
- // "The Timestamp Echo Reply field (TSecr) is only valid if the ACK
- // bit is set in the Tcp header; if it is valid, it echos a times-
- // tamp value that was sent by the remote Tcp in the TSval field
- // of a Timestamps option. When TSecr is not valid, its value
- // must be zero."
- option->setEchoedTimestamp(tcpHeader->getAckBit() ? state->ts_recent : 0);
-
- state->snd_initial_ts = true;
- state->ts_enabled = state->ts_support && state->snd_initial_ts && state->rcv_initial_ts;
- EV_INFO << "Tcp Header Option TS(TSval=" << option->getSenderTimestamp() << ", TSecr=" << option->getEchoedTimestamp() << ") sent, TS (ts_enabled) is set to " << state->ts_enabled << "\n";
+ // AccECN option on the SYN-ACK (draft-ietf-tcpm-accurate-ecn section 3.2.3):
+ // once the incoming SYN negotiated AccECN, the SYN-ACK carries the AccECN
+ // option seeding the byte counters at their wire-init offsets. Gated on
+ // getAckBit() so it rides the SYN-ACK but never the client's own initial
+ // bare SYN -- that SYN advertises AccECN with the flag-bit combination
+ // alone, no option. Only the FIRST SYN-ACK
+ // carries the option: on a SYN-ACK retransmit (syn_rexmit_count > 0) Linux
+ // conservatively omits the AccECN option -- since a middlebox that dropped
+ // the option-bearing SYN-ACK is a plausible reason for the retransmit -- so
+ // the retransmit falls back to a plain SYN-ACK (mss/WS/SACK only). The kind
+ // is fixed to ACCECN1 (Linux's first-emission ordering E1B,CEB,E0B); the
+ // post-handshake alternation start is a separate concern. This block is pure
+ // (it may run as a header-size dry run) -- it mutates no beacon state.
+ if (state->accEcnNegotiated && state->accEcnOptionEnabled && tcpHeader->getAckBit()
+ && state->syn_rexmit_count == 0) {
+ // Pad with NOPs so the options area stays 4-byte aligned once this
+ // 11-byte option is appended -- same convention as the other options.
+ while (tcpHeader->getHeaderOptionArrayLength().get() % 4 != 1)
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ TcpOptionAccEcn *option = new TcpOptionAccEcn();
+ option->setKind(TCPOPTION_ACCECN1);
+ // Wire init offsets: E0B/E1B start at 1, CEB at 0.
+ option->setEct0Bytes(state->rcvEct0Bytes + 1);
+ option->setEct1Bytes(state->rcvEct1Bytes + 1);
+ option->setCeBytes(state->rcvCeBytes);
tcpHeader->appendHeaderOption(option);
+ EV_INFO << "Tcp Header Option AccECN on SYN-ACK(kind=" << option->getKind()
+ << ", E0B=" << option->getEct0Bytes() << ", E1B=" << option->getEct1Bytes()
+ << ", CEB=" << option->getCeBytes() << ") sent\n";
}
// TODO add new TCPOptions here once they are implemented
@@ -1550,7 +3241,7 @@ TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
{
// TS header option
if (state->ts_enabled) { // Is TS enabled?
- if (!(state->sack_enabled && (state->snd_sack || state->snd_dsack))) { // if SACK is enabled and SACKs need to be added, do not add NOPs to this segment
+ if (tcpMain->alignOptions && !(state->sack_enabled && (state->snd_sack || state->snd_dsack))) { // if SACK is enabled and SACKs need to be added, do not add NOPs to this segment
// 2 padding bytes
tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
tcpHeader->appendHeaderOption(new TcpOptionNop()); // NOP
@@ -1559,16 +3250,17 @@ TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
TcpOptionTimestamp *option = new TcpOptionTimestamp();
// Update TS variables
- // RFC 1323, page 13: "The Timestamp Value field (TSval) contains the current value of the timestamp clock of the Tcp sending the option."
+ // RFC 7323, page 12: "The TSval field contains the current value of the timestamp clock of the Tcp sending the option."
option->setSenderTimestamp(convertSimtimeToTS(simTime()));
- // RFC 1323, page 16: "(3) When a TSopt is sent, its TSecr field is set to the current TS.Recent value."
- // RFC 1323, page 13:
- // "The Timestamp Echo Reply field (TSecr) is only valid if the ACK
- // bit is set in the Tcp header; if it is valid, it echos a times-
- // tamp value that was sent by the remote Tcp in the TSval field
- // of a Timestamps option. When TSecr is not valid, its value
- // must be zero."
+ // RFC 7323, page 17: "(3) When a TSopt is sent, its TSecr field is set to the current TS.Recent value."
+ // RFC 7323, page 12:
+ // "The TSecr field is valid if the ACK bit is set in the TCP header. If
+ // the ACK bit is not set in the outgoing TCP header, the sender of that
+ // segment SHOULD set the TSecr field to zero. When the ACK bit is set
+ // in an outgoing segment, the sender MUST echo a recently received
+ // TSval sent by the remote TCP in the TSval field of a Timestamps
+ // option."
option->setEchoedTimestamp(tcpHeader->getAckBit() ? state->ts_recent : 0);
EV_INFO << "Tcp Header Option TS(TSval=" << option->getSenderTimestamp() << ", TSecr=" << option->getEchoedTimestamp() << ") sent\n";
@@ -1588,14 +3280,129 @@ TcpHeader TcpConnection::writeHeaderOptions(const Ptr& tcpHeader)
// containing new data, and each of these "duplicate" ACKs SHOULD bear a
// SACK option."
if (state->sack_enabled && (state->snd_sack || state->snd_dsack)) {
- addSacks(tcpHeader);
+ // recovery is only created once the algorithm is established, so a SACK
+ // scheduled while still in SYN_RCVD has nothing to render it
+ if (auto *recovery = dynamic_cast(tcpAlgorithm->getRecovery()))
+ recovery->addSacks(tcpHeader);
}
+ // AccECN TCP option (draft-ietf-tcpm-accurate-ecn): byte-exact
+ // corroboration of the ACE mod-8 counter. Sent on every ACK-bearing
+ // segment once negotiated would be needlessly heavy for a 11-byte option whose
+ // information changes slowly; INET's own simplified beaconing policy instead
+ // sends it on every accEcnOptionBeaconAcks-th ACK-bearing segment, alternating
+ // kind 172/174 each time it's actually sent (neither kind is more "correct" than
+ // the other -- the receiver decodes either the same way once it knows which kind
+ // arrived -- alternating is purely so a single dropped option instance doesn't
+ // silently starve one field ordering's coverage).
+ //
+ // This block must be pure (read state->accEcnAckCount/accEcnOptionNextKindIsAccEcn1,
+ // never mutate them): writeHeaderOptions() also runs as a header-size "dry run"
+ // against a throwaway tmpTcpHeader (sendSegment()/sendData()'s
+ // "bytes + options_len <= snd_mss" budget calc), sometimes more than once for
+ // the very same real segment -- mutating a beacon counter here would make the
+ // cadence depend on how many dry runs happened to precede the real send, not
+ // on how many segments were actually sent. The actual, exactly-once mutation
+ // is sendToIP()'s job, mirroring where the ACE-encode block already
+ // lives for the identical "must fire exactly once, only on the genuine final
+ // send" reason.
+ // Only to a peer that has itself sent an AccECN option (Linux
+ // tp->saw_accecn_opt gates tcp_established_options' option emission):
+ // a peer that never sends one gets pure ACE-field feedback.
+ if (state->accEcnNegotiated && state->accEcnOptionEnabled && state->sawAccEcnOpt
+ && !state->accEcnOptFailSend && tcpHeader->getAckBit()) {
+ // Linux tcp_options_write: a packet leaving WITHOUT the option clears
+ // the sent-with-D-SACK marker; re-set below when the option goes out.
+ state->accEcnOptSentWithDsack = false;
+ uint32_t wouldBeAckCount = state->accEcnAckCount + 1;
+ if (state->accEcnOptionBeaconAcks > 0 && wouldBeAckCount % state->accEcnOptionBeaconAcks == 0) {
+ // Space fitting (Linux tcp_options_fit_accecn): the option's
+ // trailing counter fields are dropped one by one until the
+ // dword-aligned size fits the remaining 40-byte option budget
+ // alongside whatever TS/SACK already claimed
+ // (accecn sack_space_grab_with_ts pins an 8-byte, two-field
+ // option squeezed next to TS + two SACK blocks).
+ // Budget against Linux's CANONICAL padded layout, not INET's
+ // actual (tighter) packing: the kernel emits nop,nop,TS (12B)
+ // and nop,nop,SACK (4+8n B), and its fit decision falls out of
+ // that spacing -- the golden's field counts are only
+ // reproducible against the same arithmetic. INET's real
+ // packing is never larger, so the result always also fits.
+ uint32_t used0 = 0;
+ for (unsigned int i = 0; i < tcpHeader->getHeaderOptionArraySize(); i++) {
+ const TcpOption *opt = tcpHeader->getHeaderOption(i);
+ switch (opt->getKind()) {
+ case TCPOPTION_TIMESTAMP: used0 += 12; break;
+ case TCPOPTION_SACK: used0 += 4 + (opt->getLength() - 2); break;
+ case TCPOPTION_NO_OPERATION: break; // counted with its owner
+ default: used0 += ((uint32_t)opt->getLength() + 3) & ~3u; break;
+ }
+ }
+ // Linux tp->accecn_minlen: fields whose counters changed since
+ // the last emitted option are REQUIRED -- if not even they fit,
+ // the whole option is omitted (and the demand stays pending),
+ // rather than sending a shorter option that misses the news
+ // (sack_space_grab's final ECT0 reply: 3 SACKs + no option).
+ int requiredFields = state->accEcnOptMinFields > 0 ? state->accEcnOptMinFields : 1;
+ int numFields = 3;
+ uint32_t alignSize = 0;
+ while (numFields >= requiredFields) {
+ uint32_t optLen = 2 + 3 * numFields;
+ alignSize = (optLen + 3) & ~3u;
+ if (used0 + alignSize <= TCP_OPTIONS_MAX_SIZE.get())
+ break;
+ numFields--;
+ }
+ if (numFields < requiredFields)
+ goto accEcnOptionDone; // required fields don't fit: omit the option
+ state->accEcnOptMinFields = 0; // demand satisfied
+ // Pad with NOPs so the options area stays 4-byte aligned once
+ // the (possibly shortened) option is appended.
+ {
+ uint32_t optLen = 2 + 3 * numFields;
+ for (uint32_t i = 0; i < alignSize - optLen; i++)
+ tcpHeader->appendHeaderOption(new TcpOptionNop());
+ }
+ TcpOptionAccEcn *option = new TcpOptionAccEcn();
+ option->setLength(2 + 3 * numFields);
+ // When alternation is disabled (Linux behavior) always emit kind
+ // 174 (ACCECN1); otherwise alternate 172/174 per the beacon toggle.
+ option->setKind((!state->accEcnOptionKindAlternates || state->accEcnOptionNextKindIsAccEcn1)
+ ? TCPOPTION_ACCECN1 : TCPOPTION_ACCECN0);
+ // Wire init offsets (Verified Facts): E0B/E1B start at 1, CEB at 0.
+ option->setEct0Bytes(state->rcvEct0Bytes + 1);
+ option->setEct1Bytes(state->rcvEct1Bytes + 1);
+ option->setCeBytes(state->rcvCeBytes);
+ tcpHeader->appendHeaderOption(option);
+ EV_INFO << "Tcp Header Option AccECN(kind=" << option->getKind()
+ << ", E0B=" << option->getEct0Bytes() << ", E1B=" << option->getEct1Bytes()
+ << ", CEB=" << option->getCeBytes() << ") sent\n";
+ // Linux tcp_options_write: remember that this option went out on an
+ // ACK that also carries a D-SACK (first SACK block below rcv_nxt) --
+ // a further retransmit of that very range then proves our
+ // option-bearing ACKs are being dropped (tcp_rcv_spurious_retrans).
+ for (unsigned int i = 0; i < tcpHeader->getHeaderOptionArraySize(); i++) {
+ const TcpOptionSack *sackOpt = dynamic_cast(tcpHeader->getHeaderOption(i));
+ if (sackOpt && sackOpt->getSackItemArraySize() > 0
+ && seqLess(sackOpt->getSackItem(0).getStart(), state->rcv_nxt))
+ {
+ state->accEcnOptSentWithDsack = true;
+ state->accEcnSentDsackStart = sackOpt->getSackItem(0).getStart();
+ break;
+ }
+ }
+ }
+ }
+ accEcnOptionDone:;
// TODO add new TCPOptions here once they are implemented
// TODO delegate to TcpAlgorithm as well -- it may want to append additional options
}
if (tcpHeader->getHeaderOptionArraySize() != 0) {
+ // alignment to a 4-byte boundary
+ while (tcpHeader->getHeaderOptionArrayLength().get() % 4 != 0)
+ tcpHeader->appendHeaderOption(new TcpOptionEnd());
+
B options_len = tcpHeader->getHeaderOptionArrayLength();
if (options_len <= TCP_OPTIONS_MAX_SIZE) { // Options length allowed? - maximum: 40 Bytes
@@ -1653,7 +3460,98 @@ uint16_t TcpConnection::updateRcvWnd()
// update receive queue related state variables and statistics
updateRcvQueueVars();
- win = state->freeRcvBuffer;
+
+ // Linux tcp_select_window's ICSK_ACK_NOMEM branch: the previous arrival was
+ // dropped for want of buffer space, so this ACK closes the window outright
+ // rather than repeating an offer the socket cannot honour. One-shot, cleared
+ // where the reference clears icsk_ack.pending (tcp_event_ack_sent).
+ if (state->ackNomem) {
+ state->ackNomem = false;
+ state->rcv_wnd = 0;
+ // Linux moves rcv_wup up to rcv_nxt here, which collapses the offer in force
+ // to nothing -- without that the never-shrink rule below would resurrect the
+ // old right edge on the very next segment. The maximum ever offered
+ // (rcv_mwnd_seq) is untouched: it is a memory, not an offer.
+ state->rcv_adv = state->rcv_nxt;
+ emit(rcvAdvSignal, state->rcv_adv);
+ emit(rcvWndSignal, state->rcv_wnd);
+ return 0;
+ }
+
+ // Linux tcp_shrink_window=1 (__tcp_select_window's shrink branch): the
+ // offer follows GENUINE buffer free space -- occupancy at skb-truesize
+ // granularity scaled by the measured payload/truesize ratio -- rounded
+ // DOWN to the window scale, zeroed when free space falls under 1/16th of
+ // the full buffer (or below one MSS / one scale unit) while less than
+ // half the buffer is free, and capped by rcv_ssthresh with an ALIGN-up.
+ // The right edge may move DOWN; rcv_adv (updated below) still records the
+ // MAXIMUM ever promised, which is what acceptance checks against (Linux
+ // rcv_mwnd_seq). rcv_wnd_shrink_allowed pins the whole sequence: offers
+ // 15360 then 13312, drop at maxpromise+1, accept at maxpromise, win 0.
+ // (also active BEFORE window scaling is negotiated -- the SYN/SYN-ACK's
+ // unscaled window is the buffer-derived initial offer, and the maximum
+ // promise starts from it)
+ if (tcpMain->par("windowShrinkAllowed").boolValue()) {
+ uint32_t rcvbuf = state->rcvBufferSize > 0 ? state->rcvBufferSize : state->maxRcvBuffer;
+ uint64_t freeSpace = rcvBufOccupancy < rcvbuf
+ ? (((uint64_t)rcvbuf - rcvBufOccupancy) * rcvScalingRatio) >> 8 : 0;
+ uint64_t fullSpace = ((uint64_t)rcvbuf * rcvScalingRatio) >> 8;
+ uint32_t scaleUnit = 1u << state->rcv_wnd_scale;
+ freeSpace = (freeSpace / scaleUnit) * scaleUnit; // round_down
+ if (freeSpace < (fullSpace >> 1)) {
+ if (freeSpace < (fullSpace >> 4) || freeSpace < state->snd_mss || freeSpace < scaleUnit)
+ freeSpace = 0;
+ }
+ if (state->rcv_ssthresh > 0 && freeSpace > state->rcv_ssthresh)
+ freeSpace = ((uint64_t)(state->rcv_ssthresh + scaleUnit - 1) / scaleUnit) * scaleUnit; // ALIGN up
+ win = (uint32_t)std::min(freeSpace, TCP_MAX_WIN_SCALED);
+
+ const uint32_t maxWinShrink = (state->ws_enabled && state->rcv_wnd_scale)
+ ? (TCP_MAX_WIN << state->rcv_wnd_scale) : TCP_MAX_WIN;
+ if (win > maxWinShrink)
+ win = maxWinShrink;
+ state->rcv_adv = state->rcv_nxt + win;
+ emit(rcvAdvSignal, state->rcv_adv);
+ if (seqGreater(state->rcv_adv, state->rcv_mwnd_seq))
+ state->rcv_mwnd_seq = state->rcv_adv;
+ state->rcv_wnd = win;
+ emit(rcvWndSignal, state->rcv_wnd);
+ uint32_t scaledWin = state->rcv_wnd >> state->rcv_wnd_scale;
+ if (scaledWin > TCP_MAX_WIN)
+ scaledWin = TCP_MAX_WIN;
+ return (uint16_t)scaledWin;
+ }
+
+ // Linux tcp_space(): what the offer may promise is the room left in the receive
+ // BUFFER, charged the way the kernel charges it -- skb truesize, scaled by the
+ // measured payload/truesize ratio. That agrees with "bytes the application has
+ // not read yet" only while the reader keeps up; once unread data piles up the
+ // buffer is what runs out, and the buffer is what the peer must be told about.
+ //
+ // Only a buffer the application PINNED is accounted this way. An unpinned one
+ // grows on demand (the tcp_clamp_window path in the receive handler), so its
+ // free space is not what actually bounds the offer -- and INET's skb-truesize
+ // estimate is coarse enough that treating it as a bound would understate the
+ // window on a socket that was never short of memory in the first place.
+ if (state->rcvBufferSize > 0 && state->rcvbufLocked) {
+ win = rcvBufOccupancy < state->rcvBufferSize
+ ? (uint32_t)(((uint64_t)(state->rcvBufferSize - rcvBufOccupancy) * rcvScalingRatio) >> 8)
+ : 0;
+ // window auto-tuning: the offer also may not exceed the grown rcv_ssthresh
+ // (bounded by the clamp), which is what paces the window up from its start
+ if (state->rcv_ssthresh > 0)
+ win = std::min(win, std::min(state->rcv_ssthresh, state->window_clamp));
+ }
+ else {
+ win = state->maxRcvBuffer - receiveQueue->getAcknowledgedDataLength();
+ // window auto-tuning: the offer follows the grown rcv_ssthresh (bounded by
+ // the clamp) instead of the static advertisedWindow
+ if (state->rcv_ssthresh > 0) {
+ uint32_t tuned = std::min(state->rcv_ssthresh, state->window_clamp);
+ uint32_t ackedLen = receiveQueue->getAcknowledgedDataLength();
+ win = tuned > ackedLen ? tuned - ackedLen : 0;
+ }
+ }
// Following lines are based on [Stevens, W.R.: TCP/IP Illustrated, Volume 2, chapter 26.7, pages 878-879]:
// Don't advertise less than one full-sized segment to avoid SWS
@@ -1662,22 +3560,27 @@ uint16_t TcpConnection::updateRcvWnd()
// Do not shrink window
// (rcv_adv minus rcv_nxt) is the amount of space still available to the sender that was previously advertised
- if (win < state->rcv_adv - state->rcv_nxt)
- win = state->rcv_adv - state->rcv_nxt;
+ uint32_t stillOffered = seqGreater(state->rcv_adv, state->rcv_nxt) ? state->rcv_adv - state->rcv_nxt : 0;
+ if (win < stillOffered)
+ win = stillOffered;
// Observe upper limit for advertised window on this connection
const uint32_t maxWin = (state->ws_enabled && state->rcv_wnd_scale) ? (TCP_MAX_WIN << state->rcv_wnd_scale) : TCP_MAX_WIN; // TCP_MAX_WIN = 65535 (16 bit)
if (win > maxWin)
- win = maxWin; // Note: The window size is limited to a 16 bit value in the TCP header if WINDOW SCALE option (RFC 1323) is not used
+ win = maxWin; // Note: The window size is limited to a 16 bit value in the TCP header if WINDOW SCALE option (RFC 7323) is not used
// Note: The order of the "Do not shrink window" and "Observe upper limit" parts has been changed to the order used in FreeBSD Release 7.1
- // update rcv_adv if needed
- if (win > 0 && seqGE(state->rcv_nxt + win, state->rcv_adv)) {
- state->rcv_adv = state->rcv_nxt + win;
+ // Re-anchor the offer in force at rcv_nxt, as Linux does on every advertisement
+ // (rcv_wup = rcv_nxt; rcv_wnd = new_win). That is what makes the never-shrink
+ // rule above compare against the PREVIOUS offer rather than against a
+ // high-water mark that a long-past large window would pin forever. The
+ // high-water mark is rcv_mwnd_seq, tracked separately below.
+ state->rcv_adv = state->rcv_nxt + win;
- emit(rcvAdvSignal, state->rcv_adv);
- }
+ emit(rcvAdvSignal, state->rcv_adv);
+ if (seqGreater(state->rcv_adv, state->rcv_mwnd_seq))
+ state->rcv_mwnd_seq = state->rcv_adv;
state->rcv_wnd = win;
@@ -1686,7 +3589,7 @@ uint16_t TcpConnection::updateRcvWnd()
// scale rcv_wnd:
uint32_t scaled_rcv_wnd = state->rcv_wnd;
if (state->ws_enabled && state->rcv_wnd_scale) {
- ASSERT(state->rcv_wnd_scale <= 14); // RFC 1323, page 11: "the shift count must be limited to 14"
+ ASSERT(state->rcv_wnd_scale <= 14); // RFC 7323, page 10: "the shift count must be limited to 14"
scaled_rcv_wnd = scaled_rcv_wnd >> state->rcv_wnd_scale;
}
@@ -1698,14 +3601,20 @@ uint16_t TcpConnection::updateRcvWnd()
void TcpConnection::updateWndInfo(const Ptr& tcpHeader, bool doAlways)
{
uint32_t true_window = tcpHeader->getWindow();
- // RFC 1323, page 10:
+ // RFC 7323, page 10
// "The window field (SEG.WND) in the header of every incoming
- // segment, with the exception of SYN segments, is left-shifted
- // by Snd.Wind.Scale bits before updating SND.WND:
+ // segment, with the exception of segments, MUST be left-
+ // shifted by Snd.Wind.Shift bits before updating SND.WND:
// SND.WND = SEG.WND << Snd.Wind.Scale"
if (state->ws_enabled && !tcpHeader->getSynBit())
true_window = tcpHeader->getWindow() << state->snd_wnd_scale;
+ // Largest window the peer has ever advertised (Linux tcp_ack_update_window's
+ // tp->max_window); the forced_push heuristic in sendSegment() pushes once more
+ // than max_window/2 of unpushed data has gone out.
+ if (true_window > state->max_window)
+ state->max_window = true_window;
+
// Following lines are based on [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 982]:
if (doAlways || (tcpHeader->getAckBit()
&& (seqLess(state->snd_wl1, tcpHeader->getSequenceNo()) ||
@@ -1745,7 +3654,7 @@ void TcpConnection::sendOneNewSegment(bool fullSegmentsOnly, uint32_t congestion
// segments are transmitted. Assuming that these new segments and the
// corresponding ACKs are not dropped, this procedure allows the sender
// to infer loss using the standard Fast Retransmit threshold of three
- // duplicate ACKs [RFC2581]. This is more robust to reordered packets
+ // duplicate ACKs [RFC 2581]. This is more robust to reordered packets
// than if an old packet were retransmitted on the first or second
// duplicate ACK.
//
@@ -1766,6 +3675,7 @@ void TcpConnection::sendOneNewSegment(bool fullSegmentsOnly, uint32_t congestion
if (outstandingData + state->snd_mss <= state->snd_wnd &&
outstandingData + state->snd_mss <= congestionWindow + 2 * state->snd_mss)
{
+ // TODO review effectiveWin calculation based on how allowedToSend is calculated in sendData
// RFC 3042, page 3: "(...)the sender can only send two segments beyond the congestion window (cwnd)."
uint32_t effectiveWin = std::min(state->snd_wnd, congestionWindow) - outstandingData + 2 * state->snd_mss;
@@ -1780,8 +3690,10 @@ void TcpConnection::sendOneNewSegment(bool fullSegmentsOnly, uint32_t congestion
EV_DETAIL << "Limited Transmit algorithm enabled. Sending one new segment.\n";
uint32_t sentBytes = sendSegment(bytes);
- if (seqGreater(state->snd_nxt, state->snd_max))
+ if (seqGreater(state->snd_nxt, state->snd_max)) {
state->snd_max = state->snd_nxt;
+ emit(sndMaxSignal, state->snd_max);
+ }
emit(unackedSignal, state->snd_max - state->snd_una);
diff --git a/src/inet/transportlayer/tcp/TcpReceiveQueue.cc b/src/inet/transportlayer/tcp/TcpReceiveQueue.cc
index ac610d1d4a4..93e3db3282e 100644
--- a/src/inet/transportlayer/tcp/TcpReceiveQueue.cc
+++ b/src/inet/transportlayer/tcp/TcpReceiveQueue.cc
@@ -100,7 +100,7 @@ Packet *TcpReceiveQueue::extractBytesUpTo(uint32_t seq)
return nullptr;
}
-uint32_t TcpReceiveQueue::getAmountOfBufferedBytes()
+uint32_t TcpReceiveQueue::getAmountOfBufferedBytes() const
{
uint32_t bytes = 0;
@@ -127,7 +127,25 @@ void TcpReceiveQueue::getQueueStatus()
EV_DEBUG << "receiveQLength=" << reorderBuffer.getNumRegions() << " " << str() << "\n";
}
-uint32_t TcpReceiveQueue::getLE(uint32_t fromSeqNum)
+bool TcpReceiveQueue::findFirstDuplicateRange(uint32_t fromSeqNum, uint32_t toSeqNum, uint32_t& dupStart, uint32_t& dupEnd) const
+{
+ b fs = seqToOffset(fromSeqNum);
+ b ts = fs + B(toSeqNum - fromSeqNum);
+
+ for (int i = 0; i < reorderBuffer.getNumRegions(); i++) {
+ b s = std::max(reorderBuffer.getRegionStartOffset(i), fs);
+ b e = std::min(reorderBuffer.getRegionEndOffset(i), ts);
+ if (s < e) {
+ dupStart = offsetToSeq(s);
+ dupEnd = offsetToSeq(e);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t TcpReceiveQueue::getLE(uint32_t fromSeqNum) const
{
B fs = seqToOffset(fromSeqNum);
@@ -139,7 +157,7 @@ uint32_t TcpReceiveQueue::getLE(uint32_t fromSeqNum)
return fromSeqNum;
}
-uint32_t TcpReceiveQueue::getRE(uint32_t toSeqNum)
+uint32_t TcpReceiveQueue::getRE(uint32_t toSeqNum) const
{
B fs = seqToOffset(toSeqNum);
diff --git a/src/inet/transportlayer/tcp/TcpReceiveQueue.h b/src/inet/transportlayer/tcp/TcpReceiveQueue.h
index d9607c1a60b..615aa625164 100644
--- a/src/inet/transportlayer/tcp/TcpReceiveQueue.h
+++ b/src/inet/transportlayer/tcp/TcpReceiveQueue.h
@@ -90,10 +90,17 @@ class INET_API TcpReceiveQueue : public cObject
*/
virtual Packet *extractBytesUpTo(uint32_t seq);
+ /**
+ * Returns the amount of contiguous data available for reading.
+ */
+ virtual uint32_t getAcknowledgedDataLength() const {
+ return B(reorderBuffer.getAvailableDataLength()).get();
+ }
+
/**
* Returns the number of bytes (out-of-order-segments) currently buffered in queue.
*/
- virtual uint32_t getAmountOfBufferedBytes();
+ virtual uint32_t getAmountOfBufferedBytes() const;
/**
* Returns the number of bytes currently free (=available) in queue. freeRcvBuffer = maxRcvBuffer - usedRcvBuffer
@@ -113,12 +120,20 @@ class INET_API TcpReceiveQueue : public cObject
/**
* Returns left edge of enqueued region.
*/
- virtual uint32_t getLE(uint32_t fromSeqNum);
+ /**
+ * Returns true and sets [dupStart, dupEnd) to the lowest-sequence part of
+ * [fromSeqNum, toSeqNum) that duplicates already-buffered data (RFC 2883:
+ * the first duplicate contiguous sequence, reported as a D-SACK block).
+ * Must be queried BEFORE the segment is inserted into the queue.
+ */
+ virtual bool findFirstDuplicateRange(uint32_t fromSeqNum, uint32_t toSeqNum, uint32_t& dupStart, uint32_t& dupEnd) const;
+
+ virtual uint32_t getLE(uint32_t fromSeqNum) const;
/**
* Returns right edge of enqueued region.
*/
- virtual uint32_t getRE(uint32_t toSeqNum);
+ virtual uint32_t getRE(uint32_t toSeqNum) const;
/** Returns the minimum of first byte seq.no. in queue and rcv_nxt */
virtual uint32_t getFirstSeqNo();
diff --git a/src/inet/transportlayer/tcp/TcpSackRexmitQueue.cc b/src/inet/transportlayer/tcp/TcpSackRexmitQueue.cc
index 45c3c99ae4b..7ad7bd52f37 100644
--- a/src/inet/transportlayer/tcp/TcpSackRexmitQueue.cc
+++ b/src/inet/transportlayer/tcp/TcpSackRexmitQueue.cc
@@ -7,6 +7,8 @@
#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+#include "inet/transportlayer/tcp/TcpSendQueue.h"
+
namespace inet {
namespace tcp {
@@ -25,6 +27,7 @@ TcpSackRexmitQueue::~TcpSackRexmitQueue()
void TcpSackRexmitQueue::init(uint32_t seqNum)
{
+ invalidateCounters();
begin = seqNum;
end = seqNum;
}
@@ -55,13 +58,20 @@ std::string TcpSackRexmitQueue::detailedInfo() const
void TcpSackRexmitQueue::discardUpTo(uint32_t seqNum)
{
+ invalidateCounters();
ASSERT(seqLE(begin, seqNum) && seqLE(seqNum, end));
if (!rexmitQueue.empty()) {
auto i = rexmitQueue.begin();
while ((i != rexmitQueue.end()) && seqLE(i->endSeqNum, seqNum)) // discard/delete regions from rexmit queue, which have been acked
+ {
i = rexmitQueue.erase(i);
+ }
+
+ // prune recorded transmission boundaries the same way
+ for (auto s = xmitSegmentStarts.begin(); s != xmitSegmentStarts.end(); )
+ s = seqLess(*s, seqNum) ? xmitSegmentStarts.erase(s) : std::next(s);
if (i != rexmitQueue.end()) {
ASSERT(seqLE(i->beginSeqNum, seqNum) && seqLess(seqNum, i->endSeqNum));
@@ -69,6 +79,22 @@ void TcpSackRexmitQueue::discardUpTo(uint32_t seqNum)
}
}
+ // conn is null only when the queue is exercised standalone (unit tests); the
+ // Reno-dupack inferred-SACK emulation below is a connection-level concern.
+ if (conn != nullptr && !conn->getState()->sack_enabled && !rexmitQueue.empty())
+ {
+ auto& head = rexmitQueue.front();
+ if (head.sacked)
+ {
+ // It is not possible to have the UNA sacked; otherwise, it would
+ // have been ACKed. This is, most likely, our wrong guessing
+ // when adding Reno dupacks in the count.
+ head.lost = true;
+ head.sacked = false;
+ addInferredSack();
+ }
+ }
+
begin = seqNum;
// TESTING queue:
@@ -77,6 +103,7 @@ void TcpSackRexmitQueue::discardUpTo(uint32_t seqNum)
void TcpSackRexmitQueue::enqueueSentData(uint32_t fromSeqNum, uint32_t toSeqNum)
{
+ invalidateCounters();
ASSERT(seqLE(begin, fromSeqNum) && seqLE(fromSeqNum, end));
bool found = false;
@@ -87,10 +114,14 @@ void TcpSackRexmitQueue::enqueueSentData(uint32_t fromSeqNum, uint32_t toSeqNum)
ASSERT(seqLess(fromSeqNum, toSeqNum));
if (rexmitQueue.empty() || (end == fromSeqNum)) {
+ xmitSegmentStarts.insert(fromSeqNum); // original transmission boundary (skb start)
region.beginSeqNum = fromSeqNum;
region.endSeqNum = toSeqNum;
+ region.lost = false;
region.sacked = false;
region.rexmitted = false;
+ region.firstSentTime = region.lastSentTime = simTime();
+ region.transmitCount = 1;
rexmitQueue.push_back(region);
found = true;
fromSeqNum = toSeqNum;
@@ -114,6 +145,16 @@ void TcpSackRexmitQueue::enqueueSentData(uint32_t fromSeqNum, uint32_t toSeqNum)
while (i != rexmitQueue.end() && seqLE(i->endSeqNum, toSeqNum)) {
i->rexmitted = true;
+ i->lastSentTime = simTime();
+ i->transmitCount++;
+ // Deliberately KEEP i->lost: a lost mark persists across the
+ // retransmission until the data is cumulatively or selectively
+ // acked (Linux keeps TCPCB_LOST alongside TCPCB_SACKED_RETRANS --
+ // lost_out and retrans_out coexist in tcp_packets_in_flight()).
+ // Clearing it here made setPipe() count a retransmitted lost head
+ // under BOTH its rules ((a) not-lost and (b) retransmitted), one
+ // segment high, which starved PRR's ssthresh-pipe cap right after
+ // the fast retransmit and stalled recovery into the RTO.
fromSeqNum = i->endSeqNum;
found = true;
i++;
@@ -126,8 +167,15 @@ void TcpSackRexmitQueue::enqueueSentData(uint32_t fromSeqNum, uint32_t toSeqNum)
region.beginSeqNum = fromSeqNum;
region.endSeqNum = toSeqNum;
+ region.lost = beforeEnd ? i->lost : false;
region.sacked = beforeEnd ? i->sacked : false;
region.rexmitted = beforeEnd;
+ // a fragment split off *i is a retransmission of *i, so it inherits its
+ // transmit history; firstSentTime must stay the ORIGINAL send time for
+ // RACK's Karn check and Vegas' RTT sampling
+ region.firstSentTime = beforeEnd ? i->firstSentTime : simTime();
+ region.lastSentTime = simTime();
+ region.transmitCount = beforeEnd ? i->transmitCount + 1 : 1;
rexmitQueue.insert(i, region);
found = true;
fromSeqNum = toSeqNum;
@@ -174,8 +222,28 @@ bool TcpSackRexmitQueue::checkQueue() const
return f;
}
-void TcpSackRexmitQueue::setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum)
+void TcpSackRexmitQueue::addInferredSack()
+{
+ invalidateCounters();
+ // skip the head which is assumed to be lost
+ auto i = ++rexmitQueue.begin();
+ while (i != rexmitQueue.end() && i->sacked)
+ i++;
+ if (i != rexmitQueue.end()) {
+ i->lost = false;
+ i->sacked = true;
+ }
+}
+
+uint32_t TcpSackRexmitQueue::setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum)
{
+ invalidateCounters();
+ // lowest sequence number this call NEWLY marked sacked, skipping regions that
+ // were ever retransmitted (a SACK for a retransmission is ambiguous, Linux's
+ // !TCPCB_RETRANS rule); 0 = nothing new. Regions are kept in sequence order, so
+ // the first hit is the lowest. Consumed by the caller's reordering detection
+ // (a new SACK below the prior FACK proves reordering).
+ uint32_t newlySackedLow = 0;
if (seqLess(fromSeqNum, begin))
fromSeqNum = begin;
@@ -204,7 +272,10 @@ void TcpSackRexmitQueue::setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum)
while (i != rexmitQueue.end() && seqLE(i->endSeqNum, toSeqNum)) {
if (seqGE(i->beginSeqNum, fromSeqNum)) { // Search region in queue!
found = true;
- i->sacked = true; // set sacked bit
+ if (!i->sacked && !i->rexmitted && newlySackedLow == 0)
+ newlySackedLow = i->beginSeqNum;
+ i->lost = false;
+ i->sacked = true;
}
i++;
@@ -214,6 +285,7 @@ void TcpSackRexmitQueue::setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum)
Region region = *i;
region.endSeqNum = toSeqNum;
+ region.lost = false;
region.sacked = true;
rexmitQueue.insert(i, region);
i->beginSeqNum = toSeqNum;
@@ -224,6 +296,7 @@ void TcpSackRexmitQueue::setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum)
EV_DETAIL << "FAILED to set sacked bit for region: [" << fromSeqNum << ".." << toSeqNum << "). Not found in retransmission queue.\n";
ASSERT(checkQueue());
+ return newlySackedLow;
}
bool TcpSackRexmitQueue::getSackedBit(uint32_t seqNum) const
@@ -287,14 +360,30 @@ uint32_t TcpSackRexmitQueue::checkRexmitQueueForSackedOrRexmittedSegments(uint32
return bytes;
}
+void TcpSackRexmitQueue::markHeadLost()
+{
+ invalidateCounters();
+ ASSERT(!rexmitQueue.empty());
+ rexmitQueue.begin()->lost = true;
+}
+
+void TcpSackRexmitQueue::resetLostBit()
+{
+ invalidateCounters();
+ for (auto& elem : rexmitQueue)
+ elem.lost = false;
+}
+
void TcpSackRexmitQueue::resetSackedBit()
{
+ invalidateCounters();
for (auto& elem : rexmitQueue)
elem.sacked = false; // reset sacked bit
}
void TcpSackRexmitQueue::resetRexmittedBit()
{
+ invalidateCounters();
for (auto& elem : rexmitQueue)
elem.rexmitted = false; // reset rexmitted bit
}
@@ -376,6 +465,144 @@ void TcpSackRexmitQueue::checkSackBlock(uint32_t fromSeqNum, uint32_t& length, b
rexmitted = i->rexmitted;
}
+void TcpSackRexmitQueue::updateLost()
+{
+ invalidateCounters();
+ int numSacked = 0;
+ for (auto it = rexmitQueue.rbegin(); it != rexmitQueue.rend(); it++) {
+ if (it->sacked)
+ numSacked++;
+ if (numSacked >= conn->getState()->dupthresh && !it->sacked)
+ it->lost = true;
+ }
+}
+
+void TcpSackRexmitQueue::updateCounters() const
+{
+ uint32_t lost = 0, sacked = 0, retrans = 0;
+
+ if (countersValid) {
+#ifdef NDEBUG
+ return;
+#endif
+ }
+
+ for (const auto& region : rexmitQueue) {
+ uint32_t length = region.endSeqNum - region.beginSeqNum;
+ if (region.lost)
+ lost += length;
+ if (region.sacked)
+ sacked += length;
+ if (region.rexmitted)
+ retrans += length;
+ }
+
+ // debug builds keep walking even when the cache claims to be valid, so a
+ // mutation that forgot to invalidate is caught here and not in a fingerprint
+ ASSERT(!countersValid || (lost == lostBytes && sacked == sackedBytes && retrans == retransBytes));
+
+ lostBytes = lost;
+ sackedBytes = sacked;
+ retransBytes = retrans;
+ countersValid = true;
+}
+
+uint32_t TcpSackRexmitQueue::getLost() const
+{
+ updateCounters();
+ return lostBytes;
+}
+
+uint32_t TcpSackRexmitQueue::getSacked() const
+{
+ updateCounters();
+ return sackedBytes;
+}
+
+uint32_t TcpSackRexmitQueue::getRetrans() const
+{
+ updateCounters();
+ return retransBytes;
+}
+
+const TcpSackRexmitQueue::Region& TcpSackRexmitQueue::getRegion(uint32_t seqNum) const
+{
+ ASSERT(seqLE(begin, seqNum) && seqLess(seqNum, end));
+
+ RexmitQueue::const_iterator i = rexmitQueue.begin();
+
+ while (i != rexmitQueue.end() && seqLE(i->endSeqNum, seqNum)) // search for seqNum
+ i++;
+
+ ASSERT(i != rexmitQueue.end());
+ ASSERT(seqLE(i->beginSeqNum, seqNum) && seqLess(seqNum, i->endSeqNum));
+
+ return *i;
+}
+
+void TcpSackRexmitQueue::markLost(uint32_t fromSeqNum, uint32_t toSeqNum)
+{
+ invalidateCounters();
+ if (seqLess(fromSeqNum, begin))
+ fromSeqNum = begin;
+
+ if (seqLE(toSeqNum, fromSeqNum))
+ return;
+
+ ASSERT(seqLess(fromSeqNum, end));
+ ASSERT(seqLE(toSeqNum, end));
+
+ if (!rexmitQueue.empty()) {
+ auto i = rexmitQueue.begin();
+
+ while (i != rexmitQueue.end() && seqLE(i->endSeqNum, fromSeqNum))
+ i++;
+
+ ASSERT(i != rexmitQueue.end() && seqLE(i->beginSeqNum, fromSeqNum) && seqLess(fromSeqNum, i->endSeqNum));
+
+ if (i->beginSeqNum != fromSeqNum) { // split off the tail so lost applies exactly from fromSeqNum
+ Region region = *i;
+
+ region.endSeqNum = fromSeqNum;
+ rexmitQueue.insert(i, region);
+ i->beginSeqNum = fromSeqNum;
+ }
+
+ while (i != rexmitQueue.end() && seqLE(i->endSeqNum, toSeqNum)) {
+ if (seqGE(i->beginSeqNum, fromSeqNum) && !i->sacked)
+ i->lost = true;
+
+ i++;
+ }
+
+ if (i != rexmitQueue.end() && seqLess(i->beginSeqNum, toSeqNum) && seqLess(toSeqNum, i->endSeqNum)) {
+ Region region = *i;
+
+ region.endSeqNum = toSeqNum;
+ region.lost = !region.sacked;
+ rexmitQueue.insert(i, region);
+ i->beginSeqNum = toSeqNum;
+ }
+ }
+
+ ASSERT(checkQueue());
+}
+
+void TcpSackRexmitQueue::clearRexmitted(uint32_t fromSeqNum, uint32_t toSeqNum)
+{
+ invalidateCounters();
+ // RACK decided a RETRANSMISSION itself was lost (its send time matured
+ // against the reordering window): Linux tcp_mark_skb_lost clears
+ // TCPCB_SACKED_RETRANS (retrans_out--), which is what re-arms
+ // tcp_xmit_retransmit_queue to send the range again. The lost mark stays.
+ for (auto& region : rexmitQueue) {
+ if (seqGE(region.beginSeqNum, toSeqNum))
+ break;
+ if (seqGE(region.beginSeqNum, fromSeqNum) && region.rexmitted && !region.sacked)
+ region.rexmitted = false;
+ }
+}
+
} // namespace tcp
} // namespace inet
diff --git a/src/inet/transportlayer/tcp/TcpSackRexmitQueue.h b/src/inet/transportlayer/tcp/TcpSackRexmitQueue.h
index cf538921572..e42f39d9ec5 100644
--- a/src/inet/transportlayer/tcp/TcpSackRexmitQueue.h
+++ b/src/inet/transportlayer/tcp/TcpSackRexmitQueue.h
@@ -24,16 +24,40 @@ class INET_API TcpSackRexmitQueue
struct Region {
uint32_t beginSeqNum;
uint32_t endSeqNum;
+ bool lost; // indicates whether region has been lost
bool sacked; // indicates whether region has already been sacked by data receiver
bool rexmitted; // indicates whether region has already been retransmitted by data sender
+ simtime_t firstSentTime = 0; // time this region was first transmitted (RACK/Vegas: original send time)
+ simtime_t lastSentTime = 0; // time this region was most recently (re)transmitted (RACK: xmit time)
+ uint16_t transmitCount = 0; // number of times this region has been transmitted (1 = never retransmitted)
};
typedef std::list RexmitQueue;
RexmitQueue rexmitQueue; // rexmitQueue is ordered by seqnum, and doesn't have overlapped Regions
+ std::set xmitSegmentStarts; // begin seqnums of ORIGINAL transmissions (skb boundaries): lets RACK tell a whole small segment (advances the reference, Linux tags the skb) from a sub-MSS fragment split off a bigger segment by a byte-range SACK (never tagged, tcp_match_skb_to_sack fragments only at MSS boundaries)
+
+ bool isTransmissionStart(uint32_t seqNum) const { return xmitSegmentStarts.find(seqNum) != xmitSegmentStarts.end(); }
uint32_t begin; // 1st sequence number stored
uint32_t end; // last sequence number stored + 1
+ protected:
+ // getLost()/getSacked()/getRetrans() are read several times per ACK (setPipe alone
+ // runs 2-3 times, getBytesInFlight sums all three), so the totals are walked once
+ // and cached until something touches the queue. Linux keeps the equivalent
+ // lost_out/sacked_out/retrans_out permanently up to date at every mutation point;
+ // invalidating is the same idea with one place to get right instead of twenty.
+ mutable bool countersValid = false;
+ mutable uint32_t lostBytes = 0;
+ mutable uint32_t sackedBytes = 0;
+ mutable uint32_t retransBytes = 0;
+
+ /** Walks the queue once to refresh the cached flag totals. */
+ virtual void updateCounters() const;
+
+ /** Every insertion, removal or flag change in the queue must call this. */
+ void invalidateCounters() { countersValid = false; }
+
public:
/**
* Ctor
@@ -92,13 +116,24 @@ class INET_API TcpSackRexmitQueue
*/
virtual void enqueueSentData(uint32_t fromSeqNum, uint32_t toSeqNum);
+ /**
+ * Emulate sacks for sackless connections. Called on a new dupack, it marks
+ * one more segment as sacked.
+ */
+ virtual void addInferredSack();
+
/**
* Called when data sender received selective acknowledgments.
* Tells the queue which bytes have been transmitted and SACKed,
* so they can be skipped if retransmitting segments as long as
* REXMIT timer did not expired.
*/
- virtual void setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum);
+ /**
+ * Marks [fromSeqNum, toSeqNum) as SACKed. Returns the lowest sequence number
+ * this call NEWLY marked (skipping ever-retransmitted regions, whose SACKs are
+ * ambiguous), or 0 if nothing was newly marked -- used for reordering detection.
+ */
+ virtual uint32_t setSackedBit(uint32_t fromSeqNum, uint32_t toSeqNum);
/**
* Returns SackedBit value of seqNum.
@@ -127,6 +162,13 @@ class INET_API TcpSackRexmitQueue
*/
virtual uint32_t checkRexmitQueueForSackedOrRexmittedSegments(uint32_t fromSeq) const;
+ virtual void markHeadLost();
+
+ /**
+ * Resets lost bit of all segments in rexmit queue.
+ */
+ virtual void resetLostBit();
+
/**
* Called when REXMIT timer expired.
* Resets sacked bit of all segments in rexmit queue.
@@ -140,7 +182,7 @@ class INET_API TcpSackRexmitQueue
virtual void resetRexmittedBit();
/**
- * Returns total amount of sacked bytes. Corresponds to update() function from RFC 3517.
+ * Returns total amount of sacked bytes. Corresponds to update() function from RFC 6675.
*/
virtual uint32_t getTotalAmountOfSackedBytes() const;
@@ -160,6 +202,38 @@ class INET_API TcpSackRexmitQueue
*/
virtual void checkSackBlock(uint32_t seqNum, uint32_t& length, bool& sacked, bool& rexmitted) const;
+ virtual void updateLost();
+
+ /**
+ * Returns the total number of lost bytes in the queue.
+ */
+ virtual uint32_t getLost() const;
+
+ /**
+ * Returns the total number of sacked bytes in the queue.
+ */
+ virtual uint32_t getSacked() const;
+
+ /**
+ * Returns the total number of retransmitted bytes in the queue.
+ */
+ virtual uint32_t getRetrans() const;
+
+ /**
+ * Returns the region containing seqNum. seqNum must be within [begin, end).
+ * Used by RACK to read a segment's transmit time and count.
+ */
+ virtual const Region& getRegion(uint32_t seqNum) const;
+
+ /**
+ * Marks the byte range [fromSeqNum, toSeqNum) as lost (RACK/RFC 3517),
+ * splitting regions at the boundaries as needed.
+ */
+ virtual void markLost(uint32_t fromSeqNum, uint32_t toSeqNum);
+
+ /** RACK re-marked a lost RETRANSMISSION: clear the rexmitted flag on unsacked regions in the range (Linux tcp_mark_skb_lost clearing TCPCB_SACKED_RETRANS) so the recovery picker sends them again; the lost mark stays. */
+ virtual void clearRexmitted(uint32_t fromSeqNum, uint32_t toSeqNum);
+
protected:
/*
* Returns if TcpSackRexmitQueue is valid or not.
diff --git a/src/inet/transportlayer/tcp/TcpSimsignals.cc b/src/inet/transportlayer/tcp/TcpSimsignals.cc
new file mode 100644
index 00000000000..9161ed7e574
--- /dev/null
+++ b/src/inet/transportlayer/tcp/TcpSimsignals.cc
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2023 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
+
+namespace inet {
+namespace tcp {
+
+simsignal_t bytesInFlightSignal = cComponent::registerSignal("bytesInFlight"); // amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
+simsignal_t cwndSignal = cComponent::registerSignal("cwnd"); // will record changes to snd_cwnd
+simsignal_t dupAcksSignal = cComponent::registerSignal("dupAcks"); // current number of received dupAcks
+simsignal_t numRtosSignal = cComponent::registerSignal("numRtos"); // will record total number of RTOs
+simsignal_t pipeSignal = cComponent::registerSignal("pipe"); // current sender's estimate of bytes outstanding in the network
+simsignal_t rcvAckSignal = cComponent::registerSignal("rcvAck"); // received ackNo (=snd_una)
+simsignal_t rcvAdvSignal = cComponent::registerSignal("rcvAdv"); // current advertised window (=rcv_adv)
+simsignal_t rcvNASegSignal = cComponent::registerSignal("rcvNASeg"); // number of received not acceptable segments
+simsignal_t rcvOooSegSignal = cComponent::registerSignal("rcvOooSeg"); // number of received out-of-order segments
+simsignal_t rcvSacksSignal = cComponent::registerSignal("rcvSacks"); // number of received Sacks
+simsignal_t rcvSeqSignal = cComponent::registerSignal("rcvSeq"); // received seqNo
+simsignal_t rcvWndSignal = cComponent::registerSignal("rcvWnd"); // rcv_wnd
+simsignal_t rtoSignal = cComponent::registerSignal("rto"); // will record retransmission timeout
+simsignal_t rttSignal = cComponent::registerSignal("rtt"); // will record measured RTT
+simsignal_t rttvarSignal = cComponent::registerSignal("rttvar"); // will record RTT variance (rttvar)
+simsignal_t sackedBytesSignal = cComponent::registerSignal("sackedBytes"); // current number of received sacked bytes
+simsignal_t deliveredSignal = cComponent::registerSignal("delivered"); // cumulative newly-delivered (acked + sacked) bytes (RFC 8985/6937)
+simsignal_t sndAckSignal = cComponent::registerSignal("sndAck"); // sent ackNo
+simsignal_t sndMaxSignal = cComponent::registerSignal("sndMax"); // snd_max
+simsignal_t sndSacksSignal = cComponent::registerSignal("sndSacks"); // number of sent Sacks
+simsignal_t sndSeqSignal = cComponent::registerSignal("sndSeq"); // sent seqNo
+simsignal_t sndWndSignal = cComponent::registerSignal("sndWnd"); // snd_wnd
+simsignal_t srttSignal = cComponent::registerSignal("srtt"); // will record smoothed RTT
+simsignal_t ssthreshSignal = cComponent::registerSignal("ssthresh"); // will record changes to ssthresh
+simsignal_t stateSignal = cComponent::registerSignal("state"); // FSM state
+simsignal_t tcpRcvPayloadBytesSignal = cComponent::registerSignal("tcpRcvPayloadBytes"); // amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
+simsignal_t tcpRcvQueueBytesSignal = cComponent::registerSignal("tcpRcvQueueBytes"); // current amount of used bytes in tcp receive queue
+simsignal_t tcpRcvQueueDropsSignal = cComponent::registerSignal("tcpRcvQueueDrops"); // number of drops in tcp receive queue
+simsignal_t unackedSignal = cComponent::registerSignal("unacked"); // number of bytes unacknowledged
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/TcpSimsignals.h b/src/inet/transportlayer/tcp/TcpSimsignals.h
new file mode 100644
index 00000000000..8ce107347a1
--- /dev/null
+++ b/src/inet/transportlayer/tcp/TcpSimsignals.h
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2004 OpenSim Ltd.
+// Copyright (C) 2009-2010 Thomas Reschka
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_TCPSIGNALS_H
+#define __INET_TCPSIGNALS_H
+
+#include "inet/common/INETDefs.h"
+
+namespace inet {
+namespace tcp {
+
+extern INET_API simsignal_t bytesInFlightSignal;
+extern INET_API simsignal_t cwndSignal; // will record changes to snd_cwnd
+extern INET_API simsignal_t cwndSignal; // will record changes to snd_cwnd
+extern INET_API simsignal_t dupAcksSignal; // current number of received dupAcks
+extern INET_API simsignal_t numRtosSignal; // will record total number of RTOs
+extern INET_API simsignal_t numRtosSignal; // will record total number of RTOs
+extern INET_API simsignal_t pipeSignal; // current sender's estimate of bytes outstanding in the network
+extern INET_API simsignal_t rcvAckSignal; // received ackNo (=snd_una)
+extern INET_API simsignal_t rcvAdvSignal; // current advertised window (=rcv_adv)
+extern INET_API simsignal_t rcvNASegSignal; // number of received not acceptable segments
+extern INET_API simsignal_t rcvOooSegSignal; // number of received out-of-order segments
+extern INET_API simsignal_t rcvSacksSignal; // number of received Sacks
+extern INET_API simsignal_t rcvSeqSignal; // received seqNo
+extern INET_API simsignal_t rcvWndSignal; // rcv_wnd
+extern INET_API simsignal_t rtoSignal; // will record retransmission timeout
+extern INET_API simsignal_t rtoSignal; // will record retransmission timeout
+extern INET_API simsignal_t rttSignal; // will record measured RTT
+extern INET_API simsignal_t rttSignal; // will record measured RTT
+extern INET_API simsignal_t rttvarSignal; // will record RTT variance (rttvar)
+extern INET_API simsignal_t rttvarSignal; // will record RTT variance (rttvar)
+extern INET_API simsignal_t sackedBytesSignal; // current number of received sacked bytes
+extern INET_API simsignal_t deliveredSignal; // cumulative newly-delivered (acked + sacked) bytes (RFC 8985/6937)
+extern INET_API simsignal_t sndAckSignal; // sent ackNo
+extern INET_API simsignal_t sndMaxSignal; // snd_max
+extern INET_API simsignal_t sndSacksSignal; // number of sent Sacks
+extern INET_API simsignal_t sndSeqSignal; // sent seqNo
+extern INET_API simsignal_t sndWndSignal; // snd_wnd
+extern INET_API simsignal_t srttSignal; // will record smoothed RTT
+extern INET_API simsignal_t srttSignal; // will record smoothed RTT
+extern INET_API simsignal_t ssthreshSignal; // will record changes to ssthresh
+extern INET_API simsignal_t ssthreshSignal; // will record changes to ssthresh
+extern INET_API simsignal_t stateSignal; // FSM state
+extern INET_API simsignal_t tcpConnectionAddedSignal;
+extern INET_API simsignal_t tcpRcvPayloadBytesSignal; // amount of payload bytes received (including duplicates, out of order etc) for TCP throughput
+extern INET_API simsignal_t tcpRcvQueueBytesSignal; // current amount of used bytes in tcp receive queue
+extern INET_API simsignal_t tcpRcvQueueDropsSignal; // number of drops in tcp receive queue
+extern INET_API simsignal_t unackedSignal; // number of bytes unacknowledged
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/Changes-20051129.txt b/src/inet/transportlayer/tcp/flavours/Changes-20051129.txt
deleted file mode 100644
index 213a111fed1..00000000000
--- a/src/inet/transportlayer/tcp/flavours/Changes-20051129.txt
+++ /dev/null
@@ -1,63 +0,0 @@
-Changes from Pascal Rouseau:
-
-diff -u -r1.1 TCPReno.cc
---- TCPReno.cc 4 Aug 2005 10:49:09 -0000 1.1
-+++ TCPReno.cc 29 Nov 2005 00:19:20 -0000
-@@ -132,8 +132,9 @@
- conn->retransmitOneSegment();
-
- // enter slow start
-+ // "set cwnd to ssthresh plus 3 times the segment size." (rfc 2001)
- recalculateSlowStartThreshold();
-- state->snd_cwnd = 3*state->snd_mss; // note: in Tahoe we used one MSS not 3 (=dupacks)
-+ state->snd_cwnd = state->ssthresh + 3*state->snd_mss; // 20051129 (1)
- if (cwndVector) cwndVector->record(state->snd_cwnd);
-
- tcpEV << "set cwnd=" << state->snd_cwnd << ", ssthresh=" << state->ssthresh << "\n";
-@@ -155,6 +156,9 @@
- state->snd_cwnd += state->snd_mss;
- tcpEV << "Reno on dupAck>3: Fast Recovery: inflating cwnd by MSS, new cwnd=" << state->snd_cwnd << "\n";
- if (cwndVector) cwndVector->record(state->snd_cwnd);
-+
-+ // cwnd increased, try sending
-+ sendData(); // 20051129 (2)
- }
- }
-
-Other issues
-
-- MSS:
-
- in INET, MSS is currently defined as 1024
-
-- Retransmission timeout (issues (3))
-
- in TCPReno::processRexmitTimer(), we have
- conn->retransmitData()
- which retransmits all segments
- they suggest it should be
- conn->retransmitOneSegment()
- I tried -- it didn't look very good... it produced very long silent periods... very poor performance
- looks like it couldn't handle multiple drops (ie. when the single retransmitted segment got lost)
-
- Ahmet: after the timer expiry Ahmet Sekercioglu says: all the segments are sent.
-
-- Advertised window:
-
- btw win=14 packets vs 64K -- I think it needs to match the queue sizes.
- Currently we are allowing 128 packets to be sent while our queues are configured
- to store only 50 -- that's bound for disaster. At minimum, every queue capacity
- should be be greater than the window...
- real life window is 64K, but every host/router has surely more than 64K buffer space for queues!!!!
-
- -> yes queue capacity should be at least equal to the window capacity.
- -> says: let's set the default window size = 64 mss
-
-Retransmission timer
-
- - I think I found something else. During the startup there's a ~3s silence, although current
- retransmission timeout is only 1.5s then!!! Turns out we restart the timer every time we
- receive something, so arriving dupacks kept postponing the retransmisson -- I suppose this
- should not be done.
-
- The code is in TCPBaseAlg::receivedDataAck(uint32 firstSeqAcked)
diff --git a/src/inet/transportlayer/tcp/flavours/DcTcp.cc b/src/inet/transportlayer/tcp/flavours/DcTcp.cc
index b0c739c70b0..3635dbba903 100644
--- a/src/inet/transportlayer/tcp/flavours/DcTcp.cc
+++ b/src/inet/transportlayer/tcp/flavours/DcTcp.cc
@@ -8,6 +8,7 @@
#include // min,max
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
#include "inet/transportlayer/tcp/Tcp.h"
namespace inet {
@@ -28,171 +29,127 @@ void DcTcp::initialize()
{
TcpReno::initialize();
state->dctcp_gamma = conn->getTcpMain()->par("dctcpGamma");
+ state->ecnMarkAll = true;
}
-void DcTcp::receivedDataAck(uint32_t firstSeqAcked)
+bool DcTcp::processEce(uint32_t numBytesAcked)
{
- TcpTahoeRenoFamily::receivedDataAck(firstSeqAcked);
-
- if (state->dupacks >= state->dupthresh) {
- //
- // Perform Fast Recovery: set cwnd to ssthresh (deflating the window).
- //
- EV_INFO << "Fast Recovery: setting cwnd to ssthresh=" << state->ssthresh << "\n";
- state->snd_cwnd = state->ssthresh;
-
- conn->emit(cwndSignal, state->snd_cwnd);
+ // DCTCP replaces RFC 3168's once-per-RTT halving with a reduction proportional
+ // to the fraction of marked bytes (RFC 8257 section 3.3), so this is where its
+ // congestion response belongs: returning true tells the shared ACK path that the
+ // window has been adjusted and must not also grow for this ACK -- the role the
+ // old fork's "performSsCa" flag played.
+ if (!state || !state->ect)
+ return false;
+
+ // RFC 8257 3.3.2
+ state->dctcp_bytesAcked += numBytesAcked;
+
+ // RFC 8257 3.3.3. AccECN: when this connection negotiated
+ // AccECN, use the AccECN option's byte-exact CE evidence (deliveredCeBytes,
+ // G6/G7) instead of RFC 8257's boolean gotEce-gated approximation -- AccECN
+ // already gives the precise number of CE-marked bytes the peer reported, so
+ // the "mark this whole round's bytes_acked if ECE was ever seen" approximation
+ // isn't needed in this mode. gotEce itself is never set true for an AccECN
+ // connection in the first place (the foundation-fix guard on eceBit consumption
+ // -- see TcpConnectionRcvSegment.cc's processAckInEstabEtc()), so the two
+ // branches below are naturally mutually exclusive per connection, exactly
+ // mirroring how the two ECN modes are already mutually exclusive at negotiation
+ // time elsewhere in this workstream.
+ //
+ // deliveredCeBytes/deliveredCePkts are cumulative (updated once per ACK in
+ // processAckInEstabEtc()'s ACE block, which runs AFTER this function for the
+ // very same segment -- see that block's own comment) -- the two "Mark" fields
+ // snapshot them the same way prrDeliveredMark already does for deliveredBytes,
+ // so this round's increment is picked up on the NEXT call, one ACK later. That
+ // one-ACK lag is immaterial here: DCTCP.Alpha is a windowed EWMA over many ACKs
+ // (RFC 8257 3.3.4-3.3.6 below), not a per-ACK-exact quantity, and no CE byte is
+ // ever lost or double-counted -- each one is picked up on the very next round.
+ //
+ // Lens selection is per-CONNECTION, latched, not per-round: once
+ // dctcp_accEcnOptionSeen is set (the first time this connection ever sees a
+ // real AccECN option -- state->accEcnOptionCebDeltaValid, set by
+ // readHeaderOptions() earlier in this same segment's processing, so unlike
+ // deliveredCeBytes this flag is NOT lagged), every later round uses the
+ // byte-exact deliveredCeBytes delta exclusively, even in rounds where no
+ // option happened to arrive (deliveredCeBytes correctly contributes 0 for
+ // those -- CEB is cumulative, so the NEXT option's delta automatically covers
+ // any gap rounds, per its own design). A per-round choice ("prefer bytes when
+ // nonzero, else packets") must NOT be used: a gap round would be attributed
+ // to the packet-count estimate, and then the FOLLOWING option's cumulative
+ // delta would re-include that same gap round's bytes, double-counting them. Before the option is ever seen at all, the packet-count
+ // estimate (deliveredCePkts * snd_mss, the ACE-only estimate) is used instead
+ // of reporting zero.
+ if (state->accEcnNegotiated) {
+ if (state->accEcnOptionCebDeltaValid)
+ state->dctcp_accEcnOptionSeen = true;
+
+ uint32_t marked;
+ if (state->dctcp_accEcnOptionSeen) {
+ marked = state->deliveredCeBytes - state->dctcp_deliveredCeBytesMark;
+ state->dctcp_deliveredCeBytesMark = state->deliveredCeBytes;
+ }
+ else {
+ uint32_t cePktsThisRound = state->deliveredCePkts - state->dctcp_deliveredCePktsMark;
+ state->dctcp_deliveredCePktsMark = state->deliveredCePkts;
+ marked = cePktsThisRound * state->snd_mss;
+ }
+ EV_INFO << "DcTcp AccECN CE-byte accounting: lens=" << (state->dctcp_accEcnOptionSeen ? "bytes" : "packets")
+ << " marked=" << marked << " dctcp_bytesMarked=" << (state->dctcp_bytesMarked + marked) << "\n";
+ if (marked > 0) {
+ state->dctcp_bytesMarked += marked;
+ conn->emit(markingProbSignal, 1);
+ }
+ else {
+ conn->emit(markingProbSignal, 0);
+ }
+ }
+ else if (state->gotEce) {
+ state->dctcp_bytesMarked += numBytesAcked;
+ conn->emit(markingProbSignal, 1);
}
else {
- bool performSsCa = true; // Stands for: "perform slow start and congestion avoidance"
- if (state && state->ect) {
- // RFC 8257 3.3.1
- uint32_t bytes_acked = state->snd_una - firstSeqAcked;
-
- // bool cut = false; TODO unused?
-
- // RFC 8257 3.3.2
- state->dctcp_bytesAcked += bytes_acked;
-
- // RFC 8257 3.3.3
- if (state->gotEce) {
- state->dctcp_bytesMarked += bytes_acked;
- conn->emit(markingProbSignal, 1);
- }
- else {
- conn->emit(markingProbSignal, 0);
- }
-
- // RFC 8257 3.3.4
- if (state->snd_una > state->dctcp_windEnd) {
-
- if (state->dctcp_bytesMarked) {
- // cut = true; TODO unused?
- }
-
- // RFC 8257 3.3.5
- double ratio;
-
- ratio = ((double)state->dctcp_bytesMarked / state->dctcp_bytesAcked);
- conn->emit(loadSignal, ratio);
-
- // RFC 8257 3.3.6
- // DCTCP.Alpha = DCTCP.Alpha * (1 - g) + g * M
- state->dctcp_alpha = state->dctcp_alpha * (1 - state->dctcp_gamma) + state->dctcp_gamma * ratio;
- conn->emit(calcLoadSignal, state->dctcp_alpha);
-
- // RFC 8257 3.3.7
- state->dctcp_windEnd = state->snd_nxt;
-
- // RFC 8257 3.3.8
- state->dctcp_bytesAcked = state->dctcp_bytesMarked = 0;
- state->sndCwr = false;
- }
-
- // Applying DcTcp style cwnd update only if there was congestion and the window has not yet been reduced during current interval
- if ((state->dctcp_bytesMarked && !state->sndCwr)) {
-
- performSsCa = false;
- state->sndCwr = true;
-
- // RFC 8257 3.3.9
- state->snd_cwnd = state->snd_cwnd * (1 - state->dctcp_alpha / 2);
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- uint32_t flight_size = std::min(state->snd_cwnd, state->snd_wnd); // FIXME - Does this formula computes the amount of outstanding data?
- state->ssthresh = std::max(3 * flight_size / 4, 2 * state->snd_mss);
-
- conn->emit(ssthreshSignal, state->ssthresh);
- }
- }
-
- if (performSsCa) {
- // If ECN is not enabled or if ECN is enabled and received multiple ECE-Acks in
- // less than RTT, then perform slow start and congestion avoidance.
-
- if (state->snd_cwnd < state->ssthresh) {
- EV_INFO << "cwnd <= ssthresh: Slow Start: increasing cwnd by one SMSS bytes to ";
+ conn->emit(markingProbSignal, 0);
+ }
- // perform Slow Start. RFC 2581: "During slow start, a TCP increments cwnd
- // by at most SMSS bytes for each ACK received that acknowledges new data."
- state->snd_cwnd += state->snd_mss;
+ // RFC 8257 3.3.4
+ if (state->snd_una > state->dctcp_windEnd) {
+ // RFC 8257 3.3.5
+ double ratio;
- conn->emit(cwndSignal, state->snd_cwnd);
- conn->emit(ssthreshSignal, state->ssthresh);
+ ratio = ((double)state->dctcp_bytesMarked / state->dctcp_bytesAcked);
+ conn->emit(loadSignal, ratio);
- EV_INFO << "cwnd=" << state->snd_cwnd << "\n";
- }
- else {
- // perform Congestion Avoidance (RFC 2581)
- uint32_t incr = state->snd_mss * state->snd_mss / state->snd_cwnd;
+ // RFC 8257 3.3.6
+ // DCTCP.Alpha = DCTCP.Alpha * (1 - g) + g * M
+ state->dctcp_alpha = state->dctcp_alpha * (1 - state->dctcp_gamma) + state->dctcp_gamma * ratio;
+ conn->emit(calcLoadSignal, state->dctcp_alpha);
- if (incr == 0)
- incr = 1;
+ // RFC 8257 3.3.7
+ state->dctcp_windEnd = state->snd_nxt;
- state->snd_cwnd += incr;
+ // RFC 8257 3.3.8
+ state->dctcp_bytesAcked = state->dctcp_bytesMarked = 0;
+ state->sndCwr = false;
+ }
- conn->emit(cwndSignal, state->snd_cwnd);
- conn->emit(ssthreshSignal, state->ssthresh);
+ // Applying DcTcp style cwnd update only if there was congestion and the window has not yet been reduced during current interval
+ if (state->dctcp_bytesMarked && !state->sndCwr) {
+ state->sndCwr = true;
- //
- // Note: some implementations use extra additive constant mss / 8 here
- // which is known to be incorrect (RFC 2581 p5)
- //
- // Note 2: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
- // would require maintaining a bytes_acked variable here which we don't do
- //
+ // RFC 8257 3.3.9
+ state->snd_cwnd = state->snd_cwnd * (1 - state->dctcp_alpha / 2);
- EV_INFO << "cwnd > ssthresh: Congestion Avoidance: increasing cwnd linearly, to " << state->snd_cwnd << "\n";
- }
- }
- }
+ conn->emit(cwndSignal, state->snd_cwnd);
- if (state->sack_enabled && state->lossRecovery) {
- // RFC 3517, page 7: "Once a TCP is in the loss recovery phase the following procedure MUST
- // be used for each arriving ACK:
- //
- // (A) An incoming cumulative ACK for a sequence number greater than
- // RecoveryPoint signals the end of loss recovery and the loss
- // recovery phase MUST be terminated. Any information contained in
- // the scoreboard for sequence numbers greater than the new value of
- // HighACK SHOULD NOT be cleared when leaving the loss recovery
- // phase."
- if (seqGE(state->snd_una, state->recoveryPoint)) {
- EV_INFO << "Loss Recovery terminated.\n";
- state->lossRecovery = false;
- }
- // RFC 3517, page 7: "(B) Upon receipt of an ACK that does not cover RecoveryPoint the
- // following actions MUST be taken:
- //
- // (B.1) Use Update () to record the new SACK information conveyed
- // by the incoming ACK.
- //
- // (B.2) Use SetPipe () to re-calculate the number of octets still
- // in the network."
- else {
- // update of scoreboard (B.1) has already be done in readHeaderOptions()
- conn->setPipe();
+ uint32_t flight_size = std::min(state->snd_cwnd, state->snd_wnd); // FIXME - Does this formula computes the amount of outstanding data?
+ state->ssthresh = std::max(3 * flight_size / 4, 2 * state->snd_mss);
- // RFC 3517, page 7: "(C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more
- // segments as follows:"
- if (((int)state->snd_cwnd - (int)state->pipe) >= (int)state->snd_mss) // Note: Typecast needed to avoid prohibited transmissions
- conn->sendDataDuringLossRecoveryPhase(state->snd_cwnd);
- }
+ conn->emit(ssthreshSignal, state->ssthresh);
+ return true;
}
- // RFC 3517, pages 7 and 8: "5.1 Retransmission Timeouts
- // (...)
- // If there are segments missing from the receiver's buffer following
- // processing of the retransmitted segment, the corresponding ACK will
- // contain SACK information. In this case, a TCP sender SHOULD use this
- // SACK information when determining what data should be sent in each
- // segment of the slow start. The exact algorithm for this selection is
- // not specified in this document (specifically NextSeg () is
- // inappropriate during slow start after an RTO). A relatively
- // straightforward approach to "filling in" the sequence space reported
- // as missing should be a reasonable approach."
- sendData(false);
+ return false;
}
bool DcTcp::shouldMarkAck()
diff --git a/src/inet/transportlayer/tcp/flavours/DcTcp.h b/src/inet/transportlayer/tcp/flavours/DcTcp.h
index a0a3140e744..94334cce9b8 100644
--- a/src/inet/transportlayer/tcp/flavours/DcTcp.h
+++ b/src/inet/transportlayer/tcp/flavours/DcTcp.h
@@ -42,8 +42,8 @@ class INET_API DcTcp : public TcpReno
/** Constructor */
DcTcp();
- /** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ /** RFC 8257's proportional reduction, in place of RFC 3168's halving. */
+ virtual bool processEce(uint32_t numBytesAcked) override;
virtual bool shouldMarkAck() override;
diff --git a/src/inet/transportlayer/tcp/flavours/DcTcpFamily.cc b/src/inet/transportlayer/tcp/flavours/DcTcpFamily.cc
index c83af25f768..bdc98b20217 100644
--- a/src/inet/transportlayer/tcp/flavours/DcTcpFamily.cc
+++ b/src/inet/transportlayer/tcp/flavours/DcTcpFamily.cc
@@ -14,7 +14,7 @@ namespace tcp {
std::string DcTcpFamilyStateVariables::str() const
{
std::stringstream out;
- out << TcpTahoeRenoFamilyStateVariables::str();
+ out << TcpClassicAlgorithmBaseStateVariables::str();
out << " dctcp_alpha=" << dctcp_alpha;
out << " dctcp_windEnd=" << dctcp_windEnd;
out << " dctcp_bytesAcked=" << dctcp_bytesAcked;
@@ -27,7 +27,7 @@ std::string DcTcpFamilyStateVariables::str() const
std::string DcTcpFamilyStateVariables::detailedInfo() const
{
std::stringstream out;
- out << TcpTahoeRenoFamilyStateVariables::detailedInfo();
+ out << TcpClassicAlgorithmBaseStateVariables::detailedInfo();
out << " dctcp_alpha=" << dctcp_alpha;
out << " dctcp_windEnd=" << dctcp_windEnd;
out << " dctcp_bytesAcked=" << dctcp_bytesAcked;
@@ -38,8 +38,8 @@ std::string DcTcpFamilyStateVariables::detailedInfo() const
// ---
-DcTcpFamily::DcTcpFamily() : TcpTahoeRenoFamily(),
- state((DcTcpFamilyStateVariables *&)TcpTahoeRenoFamily::state)
+DcTcpFamily::DcTcpFamily() : TcpClassicAlgorithmBase(),
+ state((DcTcpFamilyStateVariables *&)TcpClassicAlgorithmBase::state)
{
}
diff --git a/src/inet/transportlayer/tcp/flavours/DcTcpFamily.h b/src/inet/transportlayer/tcp/flavours/DcTcpFamily.h
index f74b71abbce..19364c94c8d 100644
--- a/src/inet/transportlayer/tcp/flavours/DcTcpFamily.h
+++ b/src/inet/transportlayer/tcp/flavours/DcTcpFamily.h
@@ -8,7 +8,7 @@
#define __INET_DCTCPFAMILY_H
#include "inet/transportlayer/tcp/flavours/DcTcpFamilyState_m.h"
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
namespace inet {
namespace tcp {
@@ -16,7 +16,7 @@ namespace tcp {
/**
* Provides utility functions to implement DcTcp.
*/
-class INET_API DcTcpFamily : public TcpTahoeRenoFamily
+class INET_API DcTcpFamily : public TcpClassicAlgorithmBase
{
protected:
DcTcpFamilyStateVariables *& state; // alias to TcpAlgorithm's 'state'
diff --git a/src/inet/transportlayer/tcp/flavours/DcTcpFamilyState.msg b/src/inet/transportlayer/tcp/flavours/DcTcpFamilyState.msg
index d17e44f437f..c2fad016f23 100644
--- a/src/inet/transportlayer/tcp/flavours/DcTcpFamilyState.msg
+++ b/src/inet/transportlayer/tcp/flavours/DcTcpFamilyState.msg
@@ -5,14 +5,14 @@
//
import inet.common.INETDefs;
-import inet.transportlayer.tcp.flavours.TcpTahoeRenoFamilyState;
+import inet.transportlayer.tcp.flavours.TcpClassicAlgorithmBaseState;
namespace inet::tcp;
///
/// State variables for DcTcpFamily.
///
-struct DcTcpFamilyStateVariables extends TcpTahoeRenoFamilyStateVariables
+struct DcTcpFamilyStateVariables extends TcpClassicAlgorithmBaseStateVariables
{
@descriptor(readonly);
@@ -21,6 +21,9 @@ struct DcTcpFamilyStateVariables extends TcpTahoeRenoFamilyStateVariables
uint32_t dctcp_windEnd = snd_una;
uint32_t dctcp_bytesAcked = 0;
uint32_t dctcp_bytesMarked = 0; // amount of bytes marked
+ uint32_t dctcp_deliveredCeBytesMark = 0; // AccECN: snapshot of deliveredCeBytes as of the last receivedDataAck() call, for computing this round's byte-exact CE increment (same pattern as prrDeliveredMark for deliveredBytes)
+ uint32_t dctcp_deliveredCePktsMark = 0; // AccECN: snapshot of deliveredCePkts, for the ACE-only (no AccECN option ever seen yet) fallback estimate
+ bool dctcp_accEcnOptionSeen = false; // AccECN: latches true the first time this connection ever sees a real AccECN option; selects the byte-exact lens EXCLUSIVELY from then on (never per-round -- see receivedDataAck()'s comment for why mixing lenses per round double-counts)
double dctcp_alpha = 0;
double dctcp_gamma = 0.0625; // 1/16 (backup 0.16) TODO make it NED parameter;
};
diff --git a/src/inet/transportlayer/tcp/flavours/DumbTcp.cc b/src/inet/transportlayer/tcp/flavours/DumbTcp.cc
index 5c3627925c5..1652b914aa7 100644
--- a/src/inet/transportlayer/tcp/flavours/DumbTcp.cc
+++ b/src/inet/transportlayer/tcp/flavours/DumbTcp.cc
@@ -83,18 +83,18 @@ void DumbTcp::receiveSeqChanged()
conn->sendAck();
}
-void DumbTcp::receivedDataAck(uint32_t)
+void DumbTcp::receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength)
{
- // ack may have freed up some room in the window, try sending.
- conn->sendData(65535);
+ // TODO
}
-void DumbTcp::receivedDuplicateAck()
+void DumbTcp::receivedAckForUnackedData(uint32_t)
{
- EV_INFO << "Duplicate ACK #" << state->dupacks << "\n";
+ // ack may have freed up some room in the window, try sending.
+ conn->sendData(65535);
}
-void DumbTcp::receivedAckForDataNotYetSent(uint32_t seq)
+void DumbTcp::receivedAckForUnsentData(uint32_t seq)
{
EV_INFO << "ACK acks something not yet sent, sending immediate ACK\n";
conn->sendAck();
@@ -130,6 +130,11 @@ void DumbTcp::processEcnInEstablished()
{
}
+uint32_t DumbTcp::getBytesInFlight() const
+{
+ return state->snd_nxt - state->snd_una;
+}
+
} // namespace tcp
} // namespace inet
diff --git a/src/inet/transportlayer/tcp/flavours/DumbTcp.h b/src/inet/transportlayer/tcp/flavours/DumbTcp.h
index 4eca71bf8a1..b8599e267bb 100644
--- a/src/inet/transportlayer/tcp/flavours/DumbTcp.h
+++ b/src/inet/transportlayer/tcp/flavours/DumbTcp.h
@@ -55,11 +55,11 @@ class INET_API DumbTcp : public TcpAlgorithm
virtual void receiveSeqChanged() override;
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
- virtual void receivedDuplicateAck() override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
- virtual void receivedAckForDataNotYetSent(uint32_t seq) override;
+ virtual void receivedAckForUnsentData(uint32_t seq) override;
virtual void ackSent() override;
@@ -71,9 +71,13 @@ class INET_API DumbTcp : public TcpAlgorithm
virtual void rttMeasurementCompleteUsingTS(uint32_t echoedTS) override;
+ virtual void rttMeasurementComplete(simtime_t tSent, simtime_t tAcked) override {} // no RTT estimator in DumbTcp
+
virtual bool shouldMarkAck() override;
virtual void processEcnInEstablished() override;
+
+ virtual uint32_t getBytesInFlight() const override;
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/DumbTcpState.msg b/src/inet/transportlayer/tcp/flavours/DumbTcpState.msg
index 55590d94eb0..90835bb4449 100644
--- a/src/inet/transportlayer/tcp/flavours/DumbTcpState.msg
+++ b/src/inet/transportlayer/tcp/flavours/DumbTcpState.msg
@@ -6,7 +6,7 @@
import inet.common.INETDefs;
import inet.transportlayer.tcp_common.TcpHeader;
-import inet.transportlayer.tcp.flavours.TcpBaseAlgState;
+import inet.transportlayer.tcp.flavours.TcpAlgorithmBaseState;
namespace inet::tcp;
diff --git a/src/inet/transportlayer/tcp/flavours/README b/src/inet/transportlayer/tcp/flavours/README
index acc269b0c7b..3aada29a28e 100644
--- a/src/inet/transportlayer/tcp/flavours/README
+++ b/src/inet/transportlayer/tcp/flavours/README
@@ -18,7 +18,7 @@ What is inside these "flavour" classes?
TCP implementations may differ significantly in the presence and flavour of
congestion control, fast retransmit/recovery, selective acknowledgement and
-other schemes -- while the "base part", defined in RFC 793 is always the
+other schemes -- while the "base part", defined in RFC 9293 is always the
same. In this TCP model, this "base" part is implemented in the
TCPConnection class, while the rest is outsourced to "flavour" classes that
are used via the TCPAlgorithm interface (an abstract base class).
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.cc b/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.cc
new file mode 100644
index 00000000000..917056810fa
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.cc
@@ -0,0 +1,106 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h"
+
+namespace inet {
+namespace tcp {
+
+void Rfc5681CongestionControl::receivedAckForUnackedData(uint32_t numBytesAcked)
+{
+ ASSERT(!state->lossRecovery);
+ //"
+ // 3.1. Slow Start and Congestion Avoidance
+ // ...
+ // The slow start algorithm is used when cwnd < ssthresh, while the
+ // congestion avoidance algorithm is used when cwnd > ssthresh. When
+ // cwnd and ssthresh are equal, the sender may use either slow start or
+ // congestion avoidance.
+ //"
+ if (state->snd_cwnd < state->ssthresh)
+ slowStart(numBytesAcked);
+ else
+ congestionAvoidance(numBytesAcked);
+}
+
+void Rfc5681CongestionControl::slowStart(uint32_t numBytesAcked)
+{
+ //"
+ // During slow start, a TCP increments cwnd by at most SMSS bytes for
+ // each ACK received that cumulatively acknowledges new data. Slow
+ // start ends when cwnd exceeds ssthresh (or, optionally, when it
+ // reaches it, as noted above) or when congestion is observed. While
+ // traditionally TCP implementations have increased cwnd by precisely
+ // SMSS bytes upon receipt of an ACK covering new data, we RECOMMEND
+ // that TCP implementations increase cwnd, per:
+ //
+ // cwnd += min (N, SMSS) (2)
+ //
+ // where N is the number of previously unacknowledged bytes acknowledged
+ // in the incoming ACK. This adjustment is part of Appropriate Byte
+ // Counting [RFC3465] and provides robustness against misbehaving
+ // receivers that may attempt to induce a sender to artificially inflate
+ // cwnd using a mechanism known as "ACK Division" [SCWA99]. ACK
+ // Division consists of a receiver sending multiple ACKs for a single
+ // TCP data segment, each acknowledging only a portion of its data. A
+ // TCP that increments cwnd by SMSS for each such ACK will
+ // inappropriately inflate the amount of data injected into the network.
+ //"
+ // Cwnd-limited gate (RFC 5681 principle, Linux tcp_is_cwnd_limited): grow only
+ // while the sender actually fills the window (cwnd < 2 * max_packets_out, in
+ // segments). An application-limited flow that never fills cwnd must not be
+ // allowed to inflate it -- otherwise a trickle of ACKs grows cwnd without any
+ // evidence the path can carry it.
+ if (state->snd_effmss > 0 && (state->snd_cwnd / state->snd_effmss) >= 2 * state->maxPacketsOut) {
+ EV_DETAIL << "Not growing cwnd in slow start: not cwnd-limited\n";
+ return;
+ }
+ state->snd_cwnd += std::min(numBytesAcked, state->snd_effmss);
+ conn->emit(cwndSignal, state->snd_cwnd);
+}
+
+void Rfc5681CongestionControl::congestionAvoidance(uint32_t numBytesAcked)
+{
+ //"
+ // The RECOMMENDED way to increase cwnd during congestion avoidance is
+ // to count the number of bytes that have been acknowledged by ACKs for
+ // new data. (A drawback of this implementation is that it requires
+ // maintaining an additional state variable.) When the number of bytes
+ // acknowledged reaches cwnd, then cwnd can be incremented by up to SMSS
+ // bytes. Note that during congestion avoidance, cwnd MUST NOT be
+ // increased by more than SMSS bytes per RTT. This method both allows
+ // TCPs to increase cwnd by one segment per RTT in the face of delayed
+ // ACKs and provides robustness against ACK Division attacks.
+ //
+ // Another common formula that a TCP MAY use to update cwnd during
+ // congestion avoidance is given in equation (3):
+ //
+ // cwnd += SMSS*SMSS/cwnd (3)
+ //
+ // This adjustment is executed on every incoming ACK that acknowledges
+ // new data. Equation (3) provides an acceptable approximation to the
+ // underlying principle of increasing cwnd by 1 full-sized segment per
+ // RTT. (Note that for a connection in which the receiver is
+ // acknowledging every-other packet, (3) is less aggressive than allowed
+ // -- roughly increasing cwnd every second RTT.)
+ //
+ // Implementation Note: Since integer arithmetic is usually used in TCP
+ // implementations, the formula given in equation (3) can fail to
+ // increase cwnd when the congestion window is larger than SMSS*SMSS.
+ // If the above formula yields 0, the result SHOULD be rounded up to 1
+ // byte.
+ //"
+ // cwnd is floored at 2*SMSS everywhere it is reduced, but guard the division
+ // anyway: a zero cwnd here would be a hard crash rather than a misprediction.
+ uint32_t i = state->snd_cwnd > 0 ? state->snd_effmss * state->snd_effmss / state->snd_cwnd : state->snd_effmss;
+ if (i == 0) i = 1;
+ state->snd_cwnd += i;
+ conn->emit(cwndSignal, state->snd_cwnd);
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h b/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h
new file mode 100644
index 00000000000..0486a3f482d
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_RFC5681CONGESTIONCONTROL_H
+#define __INET_RFC5681CONGESTIONCONTROL_H
+
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+#include "inet/transportlayer/tcp/ITcpCongestionControl.h"
+#include "inet/transportlayer/tcp/TcpConnection.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Implements RFC 5681: TCP Congestion Control.
+ */
+class INET_API Rfc5681CongestionControl : public ITcpCongestionControl
+{
+ protected:
+ TcpClassicAlgorithmBaseStateVariables *state = nullptr;
+ TcpConnection *conn = nullptr;
+
+ protected:
+ virtual void slowStart(uint32_t numBytesAcked);
+ virtual void congestionAvoidance(uint32_t numBytesAcked);
+
+ public:
+ Rfc5681CongestionControl(TcpStateVariables *state, TcpConnection *conn) : state(check_and_cast(state)), conn(conn) { }
+
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) override;
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.cc b/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.cc
new file mode 100644
index 00000000000..a19c5c33956
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.cc
@@ -0,0 +1,157 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/Rfc5681Recovery.h"
+
+#include // min,max
+
+#include "inet/transportlayer/tcp/Tcp.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+#include "inet/transportlayer/tcp/TcpSendQueue.h"
+
+namespace inet {
+namespace tcp {
+
+bool Rfc5681Recovery::isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ //"
+ // DUPLICATE ACKNOWLEDGMENT: An acknowledgment is considered a
+ // "duplicate" in the following algorithms when
+ // (a) the receiver of the ACK has outstanding data,
+ //"
+ bool a = state->snd_una != state->snd_max;
+ //"
+ // (b) the incoming acknowledgment carries no data,
+ //"
+ bool b = payloadLength == 0;
+ //"
+ // (c) the SYN and FIN bits are both off,
+ //"
+ bool c = !tcpHeader->getSynBit() && !tcpHeader->getFinBit();
+ //"
+ // (d) the acknowledgment number is equal to the greatest acknowledgment
+ // received on the given connection (TCP.UNA from [RFC 793]) and
+ //"
+ bool d = tcpHeader->getAckNo() == state->snd_una;
+ //"
+ // (e) the advertised window in the incoming acknowledgment equals the
+ // advertised window in the last incoming acknowledgment.
+ //"
+ uint32_t trueWindow = tcpHeader->getWindow();
+ if (state->ws_enabled && !tcpHeader->getSynBit())
+ trueWindow = tcpHeader->getWindow() << state->snd_wnd_scale;
+ bool e = trueWindow == state->snd_wnd;
+ return a && b && c && d && e;
+}
+
+void Rfc5681Recovery::receivedAckForUnackedData(uint32_t numBytesAcked)
+{
+ ASSERT(state->lossRecovery);
+ //"
+ // 6. When the next ACK arrives that acknowledges previously
+ // unacknowledged data, a TCP MUST set cwnd to ssthresh (the value
+ // set in step 2). This is termed "deflating" the window.
+ //
+ // This ACK should be the acknowledgment elicited by the
+ // retransmission from step 3, one RTT after the retransmission
+ // (though it may arrive sooner in the presence of significant out-
+ // of-order delivery of data segments at the receiver).
+ // Additionally, this ACK should acknowledge all the intermediate
+ // segments sent between the lost segment and the receipt of the
+ // third duplicate ACK, if none of these were lost.
+ //"
+ state->snd_cwnd = state->ssthresh;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ state->lossRecovery = false;
+ EV_INFO << "Loss recovery terminated" << EV_ENDL;
+}
+
+void Rfc5681Recovery::receivedDuplicateAck()
+{
+ //"
+ // 3.2. Fast Retransmit/Fast Recovery
+ //
+ // ...
+ //
+ // The fast retransmit and fast recovery algorithms are implemented
+ // together as follows.
+ //
+ // 1. On the first and second duplicate ACKs received at a sender, a
+ // TCP SHOULD send a segment of previously unsent data per [RFC3042]
+ // provided that the receiver's advertised window allows, the total
+ // FlightSize would remain less than or equal to cwnd plus 2*SMSS,
+ // and that new data is available for transmission. Further, the
+ // TCP sender MUST NOT change cwnd to reflect these two segments
+ // [RFC3042]. Note that a sender using SACK [RFC2018] MUST NOT send
+ // new data unless the incoming duplicate acknowledgment contains
+ // new SACK information.
+ //"
+ if (state->dupacks < state->dupthresh)
+ // TODO FlightSize would remain less than or equal to cwnd plus 2*SMSS
+ conn->sendData(state->snd_cwnd);
+ //"
+ // 2. When the third duplicate ACK is received, a TCP MUST set ssthresh
+ // to no more than the value given in equation (4). When [RFC3042]
+ // is in use, additional data sent in limited transmit MUST NOT be
+ // included in this calculation.
+ //"
+ // dupacks is frozen for the duration of the recovery phase, so every further
+ // duplicate ACK arrives with dupacks == dupthresh; only the first one may enter
+ // fast retransmit, the rest fall through to step 5's send below
+ else if (state->dupacks == state->dupthresh && !state->lossRecovery) {
+ //"
+ // When a TCP sender detects segment loss using the retransmission timer
+ // and the given segment has not yet been resent by way of the
+ // retransmission timer, the value of ssthresh MUST be set to no more
+ // than the value given in equation (4):
+ //
+ // ssthresh = max (FlightSize / 2, 2*SMSS) (4)
+ //
+ // where, as discussed above, FlightSize is the amount of outstanding
+ // data in the network.
+ //"
+ uint32_t flightSize = conn->getTcpAlgorithm()->getBytesInFlight() + state->snd_effmss; // the +1 MSS accounts for the retransmitOneSegment call below
+ state->ssthresh = conn->getTcpAlgorithmForUpdate()->calculateSsthresh(flightSize);
+ conn->emit(ssthreshSignal, state->ssthresh);
+
+ //"
+ // 3. The lost segment starting at SND.UNA MUST be retransmitted and
+ // cwnd set to ssthresh plus 3*SMSS. This artificially "inflates"
+ // the congestion window by the number of segments (three) that have
+ // left the network and which the receiver has buffered.
+ //"
+ conn->retransmitOneSegment(false);
+ state->snd_cwnd = state->ssthresh; // no +3*SMSS inflation: getBytesInFlight already accounts for the 3 segments in sackedOut
+ conn->emit(cwndSignal, state->snd_cwnd);
+
+ // entering fast retransmit means starting the loss recovery phase; the ACK
+ // that ends it runs step 6 in receivedAckForUnackedData()
+ state->lossRecovery = true;
+ }
+ //"
+ // 4. For each additional duplicate ACK received (after the third),
+ // cwnd MUST be incremented by SMSS. This artificially inflates the
+ // congestion window in order to reflect the additional segment that
+ // has left the network.
+ //"
+ // "additional" is counted by arrival, not by state->dupacks: the counter is frozen
+ // at dupthresh for the whole recovery phase, so every further duplicate ACK inside
+ // it is an additional one
+ else if (state->dupacks > state->dupthresh || state->lossRecovery) {
+ state->snd_cwnd += state->snd_effmss;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ }
+ //"
+ // 5. When previously unsent data is available and the new value of
+ // cwnd and the receiver's advertised window allow, a TCP SHOULD
+ // send 1*SMSS bytes of previously unsent data.
+ //"
+ conn->sendData(state->snd_cwnd);
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.h b/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.h
new file mode 100644
index 00000000000..efc3d81d46c
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc5681Recovery.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_RFC5681RECOVERY_H
+#define __INET_RFC5681RECOVERY_H
+
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+#include "inet/transportlayer/tcp/TcpConnection.h"
+#include "inet/transportlayer/tcp/ITcpRecovery.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Implements RFC 5681: TCP Congestion Control.
+ */
+class INET_API Rfc5681Recovery : public ITcpRecovery
+{
+ protected:
+ TcpClassicAlgorithmBaseStateVariables *state = nullptr;
+ TcpConnection *conn = nullptr;
+
+ public:
+ Rfc5681Recovery(TcpStateVariables *state, TcpConnection *conn) : state(check_and_cast(state)), conn(conn) { }
+
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) override;
+
+ virtual void receivedDuplicateAck() override;
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.cc b/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.cc
new file mode 100644
index 00000000000..15a776e9b06
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.cc
@@ -0,0 +1,179 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/Rfc6582Recovery.h"
+
+#include "inet/transportlayer/tcp/flavours/Rfc5681Recovery.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+
+namespace inet {
+namespace tcp {
+
+bool Rfc6582Recovery::isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ Rfc5681Recovery rfc5681Recovery(state, conn);
+ return rfc5681Recovery.isDuplicateAck(tcpHeader, payloadLength);
+}
+
+void Rfc6582Recovery::receivedAckForUnackedData(uint32_t numBytesAcked)
+{
+ ASSERT(state->lossRecovery);
+ //"
+ // 3.2. Specification
+ // ...
+ // 3) Response to newly acknowledged data:
+ // Step 6 of [RFC5681] specifies the response to the next ACK that
+ // acknowledges previously unacknowledged data. When an ACK arrives
+ // that acknowledges new data, this ACK could be the acknowledgment
+ // elicited by the initial retransmission from fast retransmit, or
+ // elicited by a later retransmission. There are two cases:
+ //"
+ if (seqGE(state->snd_una - 1, state->recover)) {
+ //"
+ // Full acknowledgments:
+ // If this ACK acknowledges all of the data up to and including
+ // recover, then the ACK acknowledges all the intermediate segments
+ // sent between the original transmission of the lost segment and
+ // the receipt of the third duplicate ACK. Set cwnd to either (1)
+ // min (ssthresh, max(FlightSize, SMSS) + SMSS) or (2) ssthresh,
+ // where ssthresh is the value set when fast retransmit was entered,
+ // and where FlightSize in (1) is the amount of data presently
+ // outstanding. This is termed "deflating" the window. If the
+ // second option is selected, the implementation is encouraged to
+ // take measures to avoid a possible burst of data, in case the
+ // amount of data outstanding in the network is much less than the
+ // new congestion window allows. A simple mechanism is to limit the
+ // number of data packets that can be sent in response to a single
+ // acknowledgment. Exit the fast recovery procedure.
+ //"
+ state->snd_cwnd = state->ssthresh; // use option (2)
+ conn->emit(cwndSignal, state->ssthresh);
+ state->lossRecovery = false;
+ state->firstPartialACK = false;
+ EV_INFO << "Loss recovery terminated" << EV_ENDL;
+ }
+ else {
+ //"
+ // Partial acknowledgments:
+ // If this ACK does *not* acknowledge all of the data up to and
+ // including recover, then this is a partial ACK. In this case,
+ // retransmit the first unacknowledged segment. Deflate the
+ // congestion window by the amount of new data acknowledged by the
+ // Cumulative Acknowledgment field. If the partial ACK acknowledges
+ // at least one SMSS of new data, then add back SMSS bytes to the
+ // congestion window. This artificially inflates the congestion
+ // window in order to reflect the additional segment that has left
+ // the network.
+ //"
+ conn->retransmitOneSegment(false);
+
+ //"
+ // Send a new segment if permitted by the new value of
+ // cwnd. This "partial window deflation" attempts to ensure that,
+ // when fast recovery eventually ends, approximately ssthresh amount
+ // of data will be outstanding in the network. Do not exit the fast
+ // recovery procedure (i.e., if any duplicate ACKs subsequently
+ // arrive, execute step 4 of Section 3.2 of [RFC5681]).
+ //"
+ conn->sendData(state->snd_cwnd);
+
+ //"
+ // For the first partial ACK that arrives during fast recovery, also
+ // reset the retransmit timer. Timer management is discussed in
+ // more detail in Section 4.
+ //"
+ if (!state->firstPartialACK) {
+ state->firstPartialACK = true;
+ EV_DETAIL << "First partial ACK arrived during recovery, restarting REXMIT timer.\n";
+ conn->getTcpAlgorithmForUpdate()->restartRexmitTimer();
+ }
+ }
+ //"
+ // 4) Retransmit timeouts:
+ // After a retransmit timeout, record the highest sequence number
+ // transmitted in the variable recover, and exit the fast recovery
+ // procedure if applicable.
+ //
+ // Step 2 above specifies a check that the Cumulative Acknowledgment
+ // field covers more than recover. Because the acknowledgment field
+ // contains the sequence number that the sender next expects to receive,
+ // the acknowledgment "ack_number" covers more than recover when
+ //
+ // ack_number - 1 > recover;
+ //
+ // i.e., at least one byte more of data is acknowledged beyond the
+ // highest byte that was outstanding when fast retransmit was last
+ // entered.
+ //
+ // Note that in step 3 above, the congestion window is deflated after a
+ // partial acknowledgment is received. The congestion window was likely
+ // to have been inflated considerably when the partial acknowledgment
+ // was received. In addition, depending on the original pattern of
+ // packet losses, the partial acknowledgment might acknowledge nearly a
+ // window of data. In this case, if the congestion window was not
+ // deflated, the data sender might be able to send nearly a window of
+ // data back-to-back.
+ //
+ // This document does not specify the sender's response to duplicate
+ // ACKs when the fast retransmit/fast recovery algorithm is not invoked.
+ // This is addressed in other documents, such as those describing the
+ // Limited Transmit procedure [RFC3042]. This document also does not
+ // address issues of adjusting the duplicate acknowledgment threshold,
+ // but assumes the threshold specified in the IETF standards; the
+ // current standard is [RFC5681], which specifies a threshold of three
+ // duplicate acknowledgments.
+ //
+ // As a final note, we would observe that in the absence of the SACK
+ // option, the data sender is working from limited information. When
+ // the issue of recovery from multiple dropped packets from a single
+ // window of data is of particular importance, the best alternative
+ // would be to use the SACK option.
+ //"
+}
+
+void Rfc6582Recovery::receivedDuplicateAck()
+{
+ //"
+ // 3.2. Specification
+ // ...
+ // 2) Three duplicate ACKs:
+ // When the third duplicate ACK is received, the TCP sender first
+ // checks the value of recover to see if the Cumulative
+ // Acknowledgment field covers more than recover. If so, the value
+ // of recover is incremented to the value of the highest sequence
+ // number transmitted by the TCP so far. The TCP then enters fast
+ // retransmit (step 2 of Section 3.2 of [RFC5681]). If not, the TCP
+ // does not enter fast retransmit and does not reset ssthresh.
+ //"
+ if (state->dupacks == state->dupthresh) {
+ conn->getRexmitQueueForUpdate()->markHeadLost(); // update for flight size calculation
+ if (!state->lossRecovery) {
+ if (seqGreater(state->snd_una - 1, state->recover)) {
+ state->recover = state->snd_max - 1;
+
+ Rfc5681Recovery rfc5681Recovery(state, conn);
+ rfc5681Recovery.receivedDuplicateAck(); // TODO be more specific
+
+ // entering fast retransmit means starting the loss recovery phase
+ state->lossRecovery = true;
+ state->firstPartialACK = false;
+ }
+ }
+ else
+ // no per-dupack cwnd inflation (RFC 6582 step 3.4): the flight-size side
+ // is deflated instead, by inferring a SACK for this duplicate ACK, so
+ // cwnd stays an honest window rather than an inflated one
+ conn->sendData(state->snd_cwnd);
+ }
+ else {
+ Rfc5681Recovery rfc5681Recovery(state, conn);
+ rfc5681Recovery.receivedDuplicateAck(); // TODO be more specific
+ }
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.h b/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.h
new file mode 100644
index 00000000000..105540ce2e1
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc6582Recovery.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_RFC6582RECOVERY_H
+#define __INET_RFC6582RECOVERY_H
+
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+#include "inet/transportlayer/tcp/TcpConnection.h"
+#include "inet/transportlayer/tcp/ITcpRecovery.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Implements RFC 6582: The NewReno Modification to TCP's Fast Recovery Algorithm.
+ */
+class INET_API Rfc6582Recovery : public ITcpRecovery
+{
+ protected:
+ TcpClassicAlgorithmBaseStateVariables *state = nullptr;
+ TcpConnection *conn = nullptr;
+
+ public:
+ Rfc6582Recovery(TcpStateVariables *state, TcpConnection *conn) : state(check_and_cast(state)), conn(conn) { }
+
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) override;
+
+ virtual void receivedDuplicateAck() override;
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.cc b/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.cc
new file mode 100644
index 00000000000..7ed94691dcc
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.cc
@@ -0,0 +1,1596 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
+
+#include "inet/transportlayer/tcp/TcpReceiveQueue.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+#include "inet/transportlayer/tcp/TcpSendQueue.h"
+#include "inet/transportlayer/tcp/TcpSimsignals.h"
+
+namespace inet {
+namespace tcp {
+
+bool Rfc6675Recovery::isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ //"
+ // For the purposes of this specification, we define a "duplicate
+ // acknowledgment" as a segment that arrives carrying a SACK block that
+ // identifies previously unacknowledged and un-SACKed octets between
+ // HighACK and HighData. Note that an ACK which carries new SACK data
+ // is counted as a duplicate acknowledgment under this definition even
+ // if it carries new data, changes the advertised window, or moves the
+ // cumulative acknowledgment point, which is different from the
+ // definition of duplicate acknowledgment in [RFC5681].
+ //"
+
+ // TODO unfortunately these values are wrong, see other comment where they are set
+ // could be something like return state->addedSackedBytes > 0;
+// return state->sackedBytes != state->sackedBytes_old;
+ return state->snd_una == tcpHeader->getAckNo() && payloadLength == 0 && state->snd_una != state->snd_max;
+
+}
+
+//"
+// 5. Algorithm Details
+// Upon the receipt of any ACK containing SACK information, the
+// scoreboard MUST be updated via the Update () routine.
+// implemented in processSACKOption()
+//
+// If the incoming ACK is a cumulative acknowledgment, the TCP MUST
+// reset DupAcks to zero.
+// implemented in processSegment1stThru8th() and Rfc6675Recovery::processAckInEstabEtc()
+//"
+
+void Rfc6675Recovery::stepA()
+{
+ //"
+ // (A) An incoming cumulative ACK for a sequence number greater than
+ // RecoveryPoint signals the end of loss recovery, and the loss
+ // recovery phase MUST be terminated. Any information contained in
+ // the scoreboard for sequence numbers greater than the new value of
+ // HighACK SHOULD NOT be cleared when leaving the loss recovery
+ // phase.
+ //"
+ if (seqGE(state->snd_una, state->recoveryPoint)) {
+ state->lossRecovery = false;
+ if (state->prrEnabled)
+ prrEndCwndReduction(); // RFC 6937: deflate to ssthresh on leaving recovery
+ conn->getRexmitQueueForUpdate()->discardUpTo(state->snd_una);
+ }
+}
+
+void Rfc6675Recovery::stepB()
+{
+ //"
+ // (B) Upon receipt of an ACK that does not cover RecoveryPoint, the
+ // following actions MUST be taken:
+ //"
+ if (seqLE(state->snd_una, state->recoveryPoint)) {
+ //"
+ // (B.1) Use Update () to record the new SACK information conveyed
+ // by the incoming ACK.
+ // implemented by processSACKOption()
+ //"
+
+ //"
+ // (B.2) Use SetPipe () to re-calculate the number of octets still
+ // in the network.
+ //"
+ setPipe();
+ }
+}
+
+void Rfc6675Recovery::stepC()
+{
+ //"
+ // (C) If cwnd - pipe >= 1 SMSS, the sender SHOULD transmit one or more
+ // segments as follows:
+ //"
+ // "1 SMSS" is compared at the size segments are actually cut to (the
+ // options-adjusted effective MSS): Linux's equivalent gate is in PACKETS
+ // (tcp_packets_in_flight < snd_cwnd), so a PRR budget of exactly one
+ // 1000-byte segment must not be swallowed by the 12-byte timestamp
+ // overhead, or the second lost segment misses the recovery-entry burst.
+ while ((int32_t)state->snd_cwnd - (int32_t)state->pipe
+ >= (int32_t)(state->snd_effmss > 0 ? state->snd_effmss : state->snd_mss)) {
+ //"
+ // (C.1) The scoreboard MUST be queried via NextSeg () for the
+ // sequence number range of the next segment to transmit (if
+ // any), and the given segment sent. If NextSeg () returns
+ // failure (no data to send), return without sending anything
+ // (i.e., terminate steps C.1 -- C.5).
+ //"
+ uint32_t seqNum;
+ if (!nextSeg(seqNum))
+ break;
+
+ //"
+ // (C.2) If any of the data octets sent in (C.1) are below HighData,
+ // HighRxt MUST be set to the highest sequence number of the
+ // retransmitted segment unless NextSeg () rule (4) was
+ // invoked for this retransmission.
+ //"
+ if (seqLess(seqNum, state->snd_max))
+ state->highRxt = seqNum + state->snd_mss;
+
+ //"
+ // (C.3) If any of the data octets sent in (C.1) are above HighData,
+ // HighData must be updated to reflect the transmission of
+ // previously unsent data.
+ //"
+ if (seqGreater(seqNum, state->snd_max)) {
+ state->snd_max = seqNum + state->snd_mss;
+ conn->emit(sndMaxSignal, state->snd_max);
+ }
+
+ if (seqLE(seqNum + state->snd_mss, state->snd_una + state->snd_wnd)) {
+ state->snd_nxt = seqNum;
+ uint32_t sentBytes = conn->sendSegment(state->snd_mss);
+
+ // RFC 6937 accounting: sendSegment() is called here DIRECTLY (not via
+ // sendData()/retransmitOneSegment()), so the dataSent()/segmentRetransmitted()
+ // callbacks that feed prrOut never fire for these sends. Count them here, or
+ // prrOut stays 0 and PRR's sndcnt = prrDelivered - prrOut over-sends.
+ if (state->prrEnabled && state->lossRecovery)
+ state->prrOut += sentBytes;
+
+ //"
+ // (C.4) The estimate of the amount of data outstanding in the
+ // network must be updated by incrementing pipe by the number
+ // of octets transmitted in (C.1).
+ //"
+ state->pipe += sentBytes;
+ }
+ else
+ break;
+
+ //"
+ // (C.5) If cwnd - pipe >= 1 SMSS, return to (C.1)
+ //"
+ }
+}
+
+void Rfc6675Recovery::receivedAckForUnackedData(uint32_t numBytesAcked)
+{
+ ASSERT(state->lossRecovery);
+ //"
+ // Once a TCP is in the loss recovery phase, the following procedure
+ // MUST be used for each arriving ACK:
+ //"
+ // RFC 6937: while in fast recovery PRR sizes cwnd from the bytes this ACK
+ // delivered, instead of the classic inflate-per-dupack / deflate-on-exit.
+ // Runs before stepA so a recovery-ending ACK still deflates to ssthresh there.
+ if (state->prrEnabled && state->lossRecovery)
+ prrCwndReduction((int)prrNewlyDelivered(), 0, true /* snd_una advanced */);
+
+ stepA();
+ stepB();
+ stepC();
+ //"
+ // Note that steps (A) and (C) can potentially send a burst of
+ // back-to-back segments into the network if the incoming cumulative
+ // acknowledgment is for more than SMSS octets of data, or if incoming
+ // SACK blocks indicate that more than SMSS octets of data have been
+ // lost in the second half of the window.
+ //"
+}
+
+void Rfc6675Recovery::step4()
+{
+ //"
+ // (4) Invoke fast retransmit and enter loss recovery as follows:
+ //"
+ state->lossRecovery = true;
+
+ //"
+ // (4.1) RecoveryPoint = HighData
+ // When the TCP sender receives a cumulative ACK for this data
+ // octet, the loss recovery phase is terminated.
+ //"
+ state->recoveryPoint = state->snd_max;
+
+ //"
+ // (4.2) ssthresh = cwnd = (FlightSize / 2)
+ // The congestion window (cwnd) and slow start threshold
+ // (ssthresh) are reduced to half of FlightSize per [RFC5681].
+ // Additionally, note that [RFC5681] requires that any
+ // segments sent as part of the Limited Transmit mechanism not
+ // be counted in FlightSize for the purpose of the above
+ // equation.
+ //"
+ // RFC 2883/3522: capture the undo context BEFORE the reduction below, so a
+ // later D-SACK proving the retransmission spurious can restore cwnd/ssthresh.
+ if (state->lossUndoEnabled)
+ undoInit();
+
+ // Reduce cwnd/ssthresh per the connection's congestion-control flavour (Linux
+ // icsk_ca_ops->ssthresh): the default is RFC 5681/6675's max(FlightSize/2, 2*SMSS)
+ // (TcpAlgorithmBase::calculateSsthreshForFastRecovery), but CUBIC applies its own
+ // beta (cwnd*0.7) -- hardcoding FlightSize/2 here gave CUBIC connections the wrong
+ // post-recovery ssthresh (the fast_recovery/PRR scripts are all CUBIC). Capture the
+ // pre-reduction cwnd first for PRR's priorCwnd (Linux tp->prior_cwnd = tp->snd_cwnd),
+ // not the old snd_cwnd*2 which assumed a /2 factor.
+ uint32_t priorCwnd = state->snd_cwnd;
+ state->ssthresh = state->snd_cwnd = conn->getTcpAlgorithmForUpdate()->calculateSsthreshForFastRecovery();
+ conn->emit(cwndSignal, state->snd_cwnd);
+ conn->emit(ssthreshSignal, state->ssthresh);
+
+ // RFC 6937: from here on the sending rate is paced by PRR rather than by the
+ // reduced cwnd above; snapshot the pre-reduction cwnd and reset the counters.
+ if (state->prrEnabled) {
+ state->priorCwnd = priorCwnd;
+ state->prrDelivered = 0;
+ state->prrOut = 0;
+ EV_INFO << "PRR fast recovery: entering, priorCwnd=" << state->priorCwnd
+ << " ssthresh=" << state->ssthresh << "\n";
+ // Run PRR on the entry ACK itself, exactly as Linux tcp_fastretrans_alert
+ // calls tcp_cwnd_reduction() BEFORE tcp_xmit_retransmit_queue(). This
+ // clamps snd_cwnd to pipe+sndcnt (~1 segment on entry) so the
+ // retransmitOneSegment() + stepC() below send only sndcnt worth. Without
+ // it snd_cwnd stays at the full reduced ssthresh and stepC's cwnd-pipe
+ // loop floods every RACK-marked-lost segment at once -- a premature
+ // multi-segment retransmit burst (Linux sends just the first hole and
+ // paces the rest over later ACKs). step4() is only reached from the
+ // duplicate-ACK path, so snd_una has not advanced (sndUnaAdvanced=false);
+ // on the reo-timer entry there is no new delivery, prrNewlyDelivered()==0,
+ // and prrCwndReduction() is an early-return no-op (behavior unchanged).
+ prrCwndReduction((int)prrNewlyDelivered(), 0, false);
+ }
+
+ //"
+ // (4.3) Retransmit the first data segment presumed dropped -- the
+ // segment starting with sequence number HighACK + 1. To
+ // prevent repeated retransmission of the same data or a
+ // premature rescue retransmission, set both HighRxt and
+ // RescueRxt to the highest sequence number in the
+ // retransmitted segment.
+ //"
+ conn->retransmitOneSegment(false); // this also sends retransmitted segments
+
+ //"
+ // (4.4) Run SetPipe ()
+ // Set a "pipe" variable to the number of outstanding octets
+ // currently "in the pipe"; this is the data which has been
+ // sent by the TCP sender but for which no cumulative or
+ // selective acknowledgment has been received and the data has
+ // not been determined to have been dropped in the network.
+ // It is assumed that the data is still traversing the network
+ // path.
+ //"
+ setPipe();
+
+ //"
+ // (4.5) In order to take advantage of potential additional
+ // available cwnd, proceed to step (C) below.
+ //"
+ stepC();
+}
+
+void Rfc6675Recovery::receivedDuplicateAck()
+{
+ //"
+ // If the incoming ACK is a duplicate acknowledgment per the definition
+ // in Section 2 (regardless of its status as a cumulative
+ // acknowledgment), and the TCP is not currently in loss recovery, the
+ // TCP MUST increase DupAcks by one and take the following steps:
+ //"
+ if (!state->lossRecovery) {
+ //"
+ // (1) If DupAcks >= DupThresh, go to step (4).
+ // Note: This check covers the case when a TCP receives SACK
+ // information for multiple segments smaller than SMSS, which can
+ // potentially prevent IsLost() (next step) from declaring a segment
+ // as lost.
+ //"
+ if (state->dupacks >= state->dupthresh)
+ step4();
+ else {
+ //"
+ // (2) If DupAcks < DupThresh but IsLost (HighACK + 1) returns true --
+ // indicating at least three segments have arrived above the current
+ // cumulative acknowledgment point, which is taken to indicate loss
+ // -- go to step (4).
+ //"
+ if (isLost(state->snd_una + 1))
+ step4();
+ else {
+ //"
+ // (3) The TCP MAY transmit previously unsent data segments as per
+ // Limited Transmit [RFC5681], except that the number of octets
+ // which may be sent is governed by pipe and cwnd as follows:
+ //"
+
+ //"
+ // (3.1) Set HighRxt to HighACK.
+ //"
+ state->highRxt = state->snd_una;
+
+ //"
+ // (3.2) Run SetPipe ().
+ //"
+ setPipe();
+
+ //"
+ // (3.3) If (cwnd - pipe) >= 1 SMSS, there exists previously unsent
+ // data, and the receiver's advertised window allows, transmit
+ // up to 1 SMSS of data starting with the octet HighData+1 and
+ // update HighData to reflect this transmission, then return
+ // to (3.2).
+ //"
+ while ((int32_t)state->snd_cwnd - (int32_t)state->pipe >= (int32_t)state->snd_mss) {
+ uint32_t seqNum;
+ if (!nextSeg(seqNum))
+ break;
+ // Limited Transmit (RFC 3042 / RFC 6675 step 3.3) transmits only
+ // PREVIOUSLY UNSENT data (HighData+1), never a retransmission. In RACK
+ // mode (lossDetectionMode==1) nextSeg()'s rule-3 "last resort" clause
+ // would otherwise return an old unSACKed segment (== snd_una on the first
+ // SACK) and retransmit the first hole a dupack early -- Linux only
+ // retransmits once RACK's reordering timer enters recovery. Restrict this
+ // pre-recovery path to new data there; classic recovery keeps its behavior.
+ if (state->lossDetectionMode == 1 && seqLess(seqNum, state->snd_max))
+ break;
+ if (seqLE(seqNum + state->snd_mss, state->snd_una + state->snd_wnd)) {
+ state->snd_nxt = seqNum;
+ uint32_t sentBytes = conn->sendSegment(state->snd_mss);
+ state->pipe += sentBytes;
+ }
+ else
+ break;
+ }
+
+ //"
+ // (3.4) Terminate processing of this ACK.
+ //"
+ }
+ }
+ }
+ else {
+ // Already in loss recovery and this ACK is a (SACK-carrying) duplicate --
+ // snd_una did not advance. RFC 6937 PRR must still run here so cwnd tracks the
+ // bytes this ACK newly SACKed (Linux tcp_cwnd_reduction runs on EVERY ACK in
+ // recovery); without it a pure-SACK recovery leaves cwnd frozen below pipe after
+ // the entry retransmit and stalls into an RTO. sndUnaAdvanced=false.
+ if (state->prrEnabled)
+ prrCwndReduction((int)prrNewlyDelivered(), 0, false /* snd_una not advanced */);
+
+ stepA();
+ stepB();
+ stepC();
+ }
+}
+
+
+bool Rfc6675Recovery::processSACKOption(const Ptr& tcpHeader, const TcpOptionSack& option)
+{
+ if (option.getLength() % 8 != 2) {
+ EV_ERROR << "ERROR: option length incorrect\n";
+ return false;
+ }
+
+ uint n = option.getSackItemArraySize();
+ ASSERT(option.getLength() == 2 + n * 8);
+
+ if (!state->sack_enabled) {
+ EV_ERROR << "ERROR: " << n << " SACK(s) received, but sack_enabled is set to false\n";
+ return false;
+ }
+
+ if (conn->getFsmState() != TCP_S_SYN_RCVD && conn->getFsmState() != TCP_S_ESTABLISHED
+ && conn->getFsmState() != TCP_S_FIN_WAIT_1 && conn->getFsmState() != TCP_S_FIN_WAIT_2)
+ {
+ EV_ERROR << "ERROR: Tcp Header Option SACK received, but in unexpected state\n";
+ return false;
+ }
+
+ if (n > 0) { // sacks present?
+ EV_INFO << n << " SACK(s) received:\n";
+ for (uint i = 0; i < n; i++) {
+ Sack tmp;
+ tmp.setStart(option.getSackItem(i).getStart());
+ tmp.setEnd(option.getSackItem(i).getEnd());
+
+ EV_INFO << (i + 1) << ". SACK: " << tmp.str() << endl;
+
+ // check for D-SACK
+ if (i == 0 && seqLE(tmp.getEnd(), tcpHeader->getAckNo())) {
+ // RFC 2883, page 8:
+ //"
+ // In order for the sender to check that the first (D)SACK block of an
+ // acknowledgement in fact acknowledges duplicate data, the sender
+ // should compare the sequence space in the first SACK block to the
+ // cumulative ACK which is carried IN THE SAME PACKET. If the SACK
+ // sequence space is less than this cumulative ACK, it is an indication
+ // that the segment identified by the SACK block has been received more
+ // than once by the receiver. An implementation MUST NOT compare the
+ // sequence space in the SACK block to the TCP state variable snd.una
+ // (which carries the total cumulative ACK), as this may result in the
+ // wrong conclusion if ACK packets are reordered.
+ //"
+ EV_DETAIL << "Received D-SACK below cumulative ACK=" << tcpHeader->getAckNo()
+ << " D-SACK: " << tmp.str() << endl;
+ // RFC 2883: the segment identified by this block was received more
+ // than once. Record it so the loss-undo logic can detect a spurious
+ // retransmission (the RFC deliberately leaves the action unspecified).
+ state->dsackSeen = true;
+ state->dsackBytes = tmp.getEnd() - tmp.getStart();
+ // a D-SACK also reveals reordering of the (spuriously retransmitted)
+ // segment: grow the reordering degree so it stops recurring.
+ if (state->adaptiveReorderingEnabled)
+ checkSackReordering(tmp.getStart());
+ // Note: RFC 2883 does not specify what should be done in this case.
+ // RFC 2883, page 9:
+ //"
+ // 5. Detection of Duplicate Packets
+ // (...) This document does not specify what action a TCP implementation should
+ // take in these cases. The extension to the SACK option simply enables
+ // the sender to detect each of these cases.(...)
+ //"
+ }
+ else if (i == 0 && n > 1 && seqGreater(tmp.getEnd(), tcpHeader->getAckNo())) {
+ // RFC 2883, page 8:
+ //"
+ // If the sequence space in the first SACK block is greater than the
+ // cumulative ACK, then the sender next compares the sequence space in
+ // the first SACK block with the sequence space in the second SACK
+ // block, if there is one. This comparison can determine if the first
+ // SACK block is reporting duplicate data that lies above the cumulative
+ // ACK.
+ //"
+ Sack tmp2(option.getSackItem(1).getStart(), option.getSackItem(1).getEnd());
+
+ if (tmp2.contains(tmp)) {
+ EV_DETAIL << "Received D-SACK above cumulative ACK=" << tcpHeader->getAckNo()
+ << " D-SACK: " << tmp.str()
+ << ", SACK: " << tmp2.str() << endl;
+ // RFC 2883: duplicate data above the cumulative ACK; record it
+ // for the loss-undo logic.
+ state->dsackSeen = true;
+ state->dsackBytes = tmp.getEnd() - tmp.getStart();
+ // a D-SACK also reveals reordering of the (spuriously retransmitted)
+ // segment: grow the reordering degree so it stops recurring.
+ if (state->adaptiveReorderingEnabled)
+ checkSackReordering(tmp.getStart());
+ // Note: RFC 2883 does not specify what should be done in this case.
+ // RFC 2883, page 9:
+ //"
+ // 5. Detection of Duplicate Packets
+ // (...) This document does not specify what action a TCP implementation should
+ // take in these cases. The extension to the SACK option simply enables
+ // the sender to detect each of these cases.(...)
+ //"
+ }
+ }
+
+ if (seqGreater(tmp.getEnd(), tcpHeader->getAckNo()) && seqGreater(tmp.getEnd(), state->snd_una)) {
+ // FACK before this block is applied: needed to recognize that the
+ // block NEWLY sacks data below the highest already-SACKed sequence.
+ uint32_t fackBefore = conn->getRexmitQueue()->getHighestSackedSeqNum();
+ uint32_t newlySackedLow = conn->getRexmitQueueForUpdate()->setSackedBit(tmp.getStart(), tmp.getEnd());
+ // Reordering detection (Linux tcp_sacktag_one/tcp_check_sack_reordering):
+ // a never-retransmitted range newly SACKed BELOW the prior FACK proves
+ // the network delivered it out of order -- data above it arrived first.
+ // A re-reported or merely grown block returns newlySackedLow at/above
+ // fackBefore and is ignored, as are SACKs of retransmissions.
+ // F-RTO (SACK side): newly SACKed data that was never retransmitted
+ // likewise proves the original flight arrived.
+ if (state->frtoActive && newlySackedLow != 0)
+ state->frtoOrigAcked = true;
+ if (state->adaptiveReorderingEnabled && newlySackedLow != 0
+ && fackBefore != 0 && seqLess(newlySackedLow, fackBefore))
+ checkSackReordering(newlySackedLow);
+ }
+ else
+ EV_DETAIL << "Received SACK below total cumulative ACK snd_una=" << state->snd_una << "\n";
+ }
+ // Loss marking is exclusive per mode (Linux tcp_identify_packet_loss):
+ // under RACK only time-based marking below may set lost -- the DupThresh
+ // region rule would pre-mark burst holes on the first SACK, which both
+ // defeats the reordering-window timer (already-lost regions are skipped
+ // as candidates) and over-counts small (sub-MSS) SACKed regions against
+ // a segment threshold.
+ if (state->lossDetectionMode != 1)
+ conn->getRexmitQueueForUpdate()->updateLost();
+
+ state->rcv_sacks += n; // total counter, no current number
+
+ conn->emit(rcvSacksSignal, state->rcv_sacks);
+
+ // update scoreboard
+ // TODO this is wrong because discardUpTo can delete sackedBytes,
+ // and the SACK option can add the same amount of new sack bytes
+ // leading to no changes in number of sacked bytes?
+ state->sackedBytes_old = state->sackedBytes; // needed for RFC 3042 to check if last dupAck contained new sack information
+ state->sackedBytes = conn->getRexmitQueue()->getTotalAmountOfSackedBytes();
+
+ conn->emit(sackedBytesSignal, state->sackedBytes);
+
+ // delivered-bytes accounting (RFC 8985/6937): count bytes newly SACKed by
+ // this segment. Cumulatively-acked bytes are counted separately where
+ // snd_una advances (process_ACK).
+ if (state->sackedBytes > state->sackedBytes_old) {
+ state->deliveredBytes += state->sackedBytes - state->sackedBytes_old;
+ conn->emit(deliveredSignal, (unsigned long)state->deliveredBytes);
+ }
+
+ // RACK time-based loss detection runs on every ACK carrying new SACK info
+ if (state->lossDetectionMode == 1)
+ rackDetectAndMarkLost();
+ }
+ return true;
+}
+
+bool Rfc6675Recovery::isLost(uint32_t seqNum)
+{
+ ASSERT(state->sack_enabled);
+
+ // RFC 6675, page 5:
+ //"
+ // This routine returns whether the given sequence number is
+ // considered to be lost. The routine returns true when either
+ // DupThresh discontiguous SACKed sequences have arrived above
+ // 'SeqNum' or more than (DupThresh - 1) * SMSS bytes with
+ // numbers greater than 'SeqNum' have been SACKed. Otherwise,
+ // the routine returns false.
+ //"
+ ASSERT(seqGE(seqNum, state->snd_una)); // HighAck = snd_una - 1
+
+ // RACK mode: a segment is lost iff RACK has marked its region lost (by time).
+ // A seqNum not tracked by the rexmit queue (below its start, or at/above its
+ // end) has no region and therefore cannot be marked lost -- guard getRegion,
+ // whose precondition is begin <= seqNum < end. This can happen for a segment
+ // whose range was already discarded, or before anything is queued (e.g. the
+ // TFO fallback path that re-sends a SYN).
+ if (state->lossDetectionMode == 1) {
+ auto rexmitQueue = conn->getRexmitQueue();
+ if (rexmitQueue->getQueueLength() == 0
+ || seqLess(seqNum, rexmitQueue->getBufferStartSeq())
+ || seqGE(seqNum, rexmitQueue->getBufferEndSeq()))
+ return false;
+ return rexmitQueue->getRegion(seqNum).lost;
+ }
+
+ // state->reordering equals state->dupthresh unless adaptive reordering has grown
+ // it (static DupThresh otherwise), so this is inert by default.
+ bool isLost = (conn->getRexmitQueue()->getNumOfDiscontiguousSacks(seqNum) >= state->reordering
+ || conn->getRexmitQueue()->getAmountOfSackedBytes(seqNum) > (state->reordering - 1) * state->snd_mss);
+
+ return isLost;
+}
+
+uint32_t Rfc6675Recovery::rackDetectAndMarkLost(bool fromReoTimer)
+{
+ if (conn->getRexmitQueue() == nullptr || !state->sack_enabled)
+ return 0;
+
+ // (1) advance the RACK reference: the most recently *sent* segment among those
+ // that have been delivered (SACKed). Skip retransmitted segments whose RTT is
+ // below the connection minimum RTT (ambiguous, Karn-style).
+ for (const auto& region : conn->getRexmitQueue()->rexmitQueue) {
+ if (!region.sacked)
+ continue;
+ // Skip a sub-MSS SACKed TAIL fragment: Linux's tcp_match_skb_to_sack
+ // fragments a partially-covered skb only at MSS boundaries, so a lone
+ // byte-range SACK of a bigger skb's tail never gets tagged and never
+ // advances the kernel's RACK reference -- TLP fires there instead of a
+ // RACK retransmit. A WHOLE small skb (e.g. a fully-SACKed 400B MSG_EOR
+ // chunk) IS tagged and DOES advance RACK, so only the buffer-tail
+ // fragment case is skipped.
+ // A region that STARTS at a genuine transmission boundary is a whole
+ // (small) segment, not a split-off fragment -- Linux tags it, so it
+ // must advance the reference.
+ if (state->snd_mss > 0 && region.endSeqNum - region.beginSeqNum < state->snd_mss
+ && region.endSeqNum == state->snd_max
+ && !conn->getRexmitQueue()->isTransmissionStart(region.beginSeqNum))
+ continue;
+ simtime_t xmit = region.lastSentTime;
+ simtime_t rtt = simTime() - xmit;
+ if (region.transmitCount > 1 && state->minRtt > 0 && rtt < state->minRtt)
+ continue;
+ if (xmit > state->rackXmitTime
+ || (xmit == state->rackXmitTime && seqGreater(region.endSeqNum, state->rackEndSeq)))
+ {
+ state->rackXmitTime = xmit;
+ state->rackEndSeq = region.endSeqNum;
+ state->rackRtt = rtt;
+ }
+ }
+
+ if (state->rackXmitTime == 0)
+ return 0;
+
+ // (2) reordering window (Linux tcp_rack_reo_wnd): the default is a min_rtt/4
+ // settling delay (capped at srtt/8) to tolerate mild reordering. Only when
+ // reordering has NEVER been observed on the connection may RACK be aggressive
+ // (reo_wnd = 0) -- and then only during recovery, or once DupThresh-worth of
+ // segments are already SACKed (the classic dupthresh entry point). The
+ // inverse rule (0 by default, min_rtt/4 after reordering) would let a single
+ // SACK mark same-burst segments lost and enter recovery on the FIRST dupack.
+ simtime_t reoWnd;
+ // Linux's tcp_rack_reo_wnd input is tp->sacked_out, a PACKET count: divide
+ // by the options-adjusted effective MSS, the size data segments are
+ // actually cut to -- dividing by snd_mss undercounts (3 sacked 1000-byte
+ // segments / mss 1012 = 2 < DupThresh) and misses the aggressive reo_wnd=0
+ // clause, deferring recovery entry to the quantized reo timer where Linux
+ // enters on the ACK itself.
+ uint32_t segSize = state->snd_effmss > 0 ? state->snd_effmss : state->snd_mss;
+ uint32_t sackedSegs = segSize > 0 ? state->sackedBytes / segSize : 0;
+ if (!state->rackReordSeen && (state->lossRecovery || sackedSegs >= state->reordering))
+ reoWnd = 0;
+ else {
+ // minRtt is only populated once a data RTT has been measured; on the very
+ // first flight (dupacks arriving before any cumulative ACK) it is still 0.
+ // Linux's min_rtt is seeded from the handshake, so it is never 0 by the
+ // time SACKs arrive -- approximate that with this ACK's own RACK RTT.
+ simtime_t minRtt = state->minRtt > 0 ? state->minRtt : state->rackRtt;
+ reoWnd = minRtt / 4;
+ if (state->srtt > 0 && state->srtt / 8 < reoWnd)
+ reoWnd = state->srtt / 8;
+ }
+
+ // (3) mark as lost any earlier-sent, still-unacked segment for which at least
+ // RACK.rtt + reo_wnd has elapsed since it was (last) sent. The comparison is
+ // INCLUSIVE (Linux tcp_rack_detect_loss marks on remaining <= 0, i.e.
+ // elapsed >= rtt + reo_wnd): with a whole flight transmitted in one burst --
+ // the norm in a discrete-event simulation, where every segment of a window
+ // carries the IDENTICAL send timestamp -- a lost head segment's elapsed time
+ // always exactly EQUALS the RACK RTT derived from its SACKed burst-mates
+ // (both measure simTime() - burstTime), so a strict > could never mark it,
+ // no matter how much time passed, and recovery stalled into an RTO.
+ std::vector> toMark;
+ std::vector> toClearRexmit;
+ simtime_t minRemaining = SIMTIME_MAX; // earliest not-yet-matured deadline
+ for (const auto& region : conn->getRexmitQueue()->rexmitQueue) {
+ if (region.sacked)
+ continue;
+ // A lost region whose RETRANSMISSION is still presumed in flight is a
+ // candidate too: its lastSentTime is the retransmit time, and if that
+ // matures against the reordering window (a SACK arrived for data sent
+ // AFTER the retransmission), the retransmission itself was lost --
+ // Linux tcp_mark_skb_lost then clears TCPCB_SACKED_RETRANS so the
+ // range is sent once more.
+ // A lost region already awaiting (re)transmission needs nothing.
+ if (region.lost && !region.rexmitted)
+ continue;
+ bool earlier = (region.lastSentTime < state->rackXmitTime)
+ || (region.lastSentTime == state->rackXmitTime && seqLE(region.endSeqNum, state->rackEndSeq));
+ if (!earlier)
+ continue;
+ simtime_t remaining = state->rackRtt + reoWnd - (simTime() - region.lastSentTime);
+ if (remaining <= 0) {
+ if (region.lost)
+ toClearRexmit.push_back(std::make_pair(region.beginSeqNum, region.endSeqNum));
+ else
+ toMark.push_back(std::make_pair(region.beginSeqNum, region.endSeqNum));
+ }
+ else if (remaining < minRemaining)
+ minRemaining = remaining;
+ }
+
+ uint32_t lostBytes = 0;
+ for (auto& r : toMark) {
+ conn->getRexmitQueueForUpdate()->markLost(r.first, r.second);
+ lostBytes += r.second - r.first;
+ }
+ for (auto& r : toClearRexmit) {
+ conn->getRexmitQueueForUpdate()->clearRexmitted(r.first, r.second);
+ lostBytes += r.second - r.first;
+ EV_INFO << "RACK: retransmission of [" << r.first << ", " << r.second << ") presumed lost, will re-send\n";
+ }
+ if (lostBytes > 0)
+ EV_INFO << "RACK: marked " << lostBytes << " bytes lost by time (RACK.rtt=" << state->rackRtt << ")\n";
+
+ // Arm the RACK reordering timer for the earliest deadline that has not matured
+ // yet (Linux ICSK_TIME_REO_TIMEOUT): dupacks stop arriving once the receiver has
+ // ACKed everything it got, so without this timer a deadline maturing between ACKs
+ // -- e.g. on a tail flight -- would only ever be noticed by the much later RTO.
+ // When the ACK path itself marks segments lost, arm at ZERO delay instead: the
+ // marking happens during SACK processing, BEFORE the cumulative ACK advances
+ // snd_una, so recovery entry must be deferred past the current event (Linux runs
+ // tcp_fastretrans_alert after tcp_clean_rtx_queue for the same reason). The
+ // timer handler re-runs detection and acts on the standing lost marks. From the
+ // timer handler itself the caller acts directly, so only the not-yet-matured
+ // deadline (if any) is re-armed there.
+ simtime_t armDelay = minRemaining != SIMTIME_MAX ? minRemaining : simtime_t(-1);
+ if (lostBytes > 0 && !fromReoTimer)
+ armDelay = SIMTIME_ZERO;
+ conn->rescheduleRackReoTimer(armDelay);
+
+ return lostBytes;
+}
+
+void Rfc6675Recovery::undoInit()
+{
+ // Linux tcp_init_undo(): remember the pre-reduction cwnd/ssthresh so a later
+ // D-SACK (or Eifel timestamp) can restore them. Must be called BEFORE the
+ // ssthresh/cwnd reduction. undoRetrans starts at -1 ("no retransmit yet"),
+ // becomes >0 as retransmissions go out, and returns to 0 once every one of
+ // them is confirmed spurious by a D-SACK.
+ state->undoMarker = state->snd_una ? state->snd_una : 1; // nonzero marker
+ state->priorSsthresh = state->ssthresh;
+ state->priorCwnd = state->snd_cwnd;
+ state->undoRetrans = -1;
+ state->retransStampTS = 0;
+}
+
+bool Rfc6675Recovery::packetDelayed() const
+{
+ // Eifel (RFC 3522 / Linux tcp_packet_delayed): the most recent ACK echoed a
+ // timestamp OLDER than our first retransmission's send time, so the receiver
+ // generated it from the ORIGINAL transmission -- the retransmission (and the
+ // congestion response that came with it) was spurious.
+ return state->ts_enabled && state->retransStampTS != 0
+ && state->lastRcvdTSecr != 0
+ && seqLess(state->lastRcvdTSecr, state->retransStampTS);
+}
+
+bool Rfc6675Recovery::mayUndo() const
+{
+ // Linux tcp_may_undo(): undo when every retransmission of the episode has been
+ // D-SACKed (undoRetrans == 0), or when the Eifel timestamp test proves the
+ // retransmission was answered from the original transmission.
+ return state->undoMarker != 0 && (state->undoRetrans == 0 || packetDelayed());
+}
+
+void Rfc6675Recovery::undoCwndReduction()
+{
+ // Linux tcp_undo_cwnd_reduction(): restore cwnd and ssthresh.
+ state->snd_cwnd = std::max(state->snd_cwnd, state->priorCwnd); // tcp_reno_undo_cwnd
+ if (state->priorSsthresh > state->ssthresh)
+ state->ssthresh = state->priorSsthresh;
+ state->undoMarker = 0;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ conn->emit(ssthreshSignal, state->ssthresh);
+ EV_INFO << "Undoing spurious cwnd reduction (D-SACK): cwnd=" << state->snd_cwnd
+ << ", ssthresh=" << state->ssthresh << "\n";
+}
+
+uint32_t Rfc6675Recovery::prrNewlyDelivered() const
+{
+ // bytes newly cumulatively-acked + selectively-acked by the ACK being processed
+ // (snapshot taken at the top of process_RCV_SEGMENT)
+ return (uint32_t)(state->deliveredBytes - state->prrDeliveredMark);
+}
+
+void Rfc6675Recovery::prrCwndReduction(int newlyAckedSacked, int newlyLost, bool sndUnaAdvanced)
+{
+ // RFC 6937 / Linux tcp_cwnd_reduction(): proportional rate reduction. All
+ // quantities are in bytes (Linux counts packets); 1 packet == snd_mss bytes.
+ if (newlyAckedSacked <= 0 || state->priorCwnd == 0)
+ return;
+
+ setPipe();
+ int pipeNow = (int)state->pipe;
+ int delta = (int)state->ssthresh - pipeNow;
+
+ state->prrDelivered += newlyAckedSacked;
+
+ int sndcnt;
+ if (delta < 0) {
+ // proportional phase: bound sending to the reduction slope
+ uint64_t dividend = (uint64_t)state->ssthresh * state->prrDelivered + state->priorCwnd - 1;
+ sndcnt = (int)(dividend / state->priorCwnd) - (int)state->prrOut;
+ }
+ else {
+ // slow-start-reduction-bound phase
+ sndcnt = std::max((int)state->prrDelivered - (int)state->prrOut, newlyAckedSacked);
+ if (sndUnaAdvanced && newlyLost == 0)
+ sndcnt += (int)state->snd_mss;
+ sndcnt = std::min(delta, sndcnt);
+ }
+ // force at least one segment out on entering fast recovery (prrOut == 0)
+ sndcnt = std::max(sndcnt, (int)(state->prrOut ? 0 : state->snd_mss));
+
+ state->snd_cwnd = (uint32_t)std::max(0, pipeNow + sndcnt);
+ conn->emit(cwndSignal, state->snd_cwnd);
+
+ EV_DETAIL << "PRR: pipe=" << pipeNow << " ssthresh=" << state->ssthresh
+ << " prrDelivered=" << state->prrDelivered << " prrOut=" << state->prrOut
+ << " sndcnt=" << sndcnt << " -> cwnd=" << state->snd_cwnd << "\n";
+}
+
+void Rfc6675Recovery::prrEndCwndReduction()
+{
+ // RFC 6937 / Linux tcp_end_cwnd_reduction(): set cwnd to ssthresh on leaving recovery.
+ state->snd_cwnd = state->ssthresh;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ EV_INFO << "PRR: leaving fast recovery, cwnd=ssthresh=" << state->snd_cwnd << "\n";
+}
+
+void Rfc6675Recovery::checkSackReordering(uint32_t lowSeq)
+{
+ // Linux tcp_check_sack_reordering(): reordering is proven when data at lowSeq
+ // was delivered while a higher sequence number (fack) had already been SACKed.
+ auto rexmitQueue = conn->getRexmitQueue();
+ if (rexmitQueue == nullptr || !state->sack_enabled)
+ return;
+ uint32_t fack = rexmitQueue->getHighestSackedSeqNum();
+ if (fack == 0 || seqGE(lowSeq, fack))
+ return;
+ uint32_t metric = fack - lowSeq;
+ if (state->snd_mss != 0 && metric > state->reordering * state->snd_mss) {
+ uint32_t newReordering = (metric + state->snd_mss - 1) / state->snd_mss;
+ state->reordering = std::min(newReordering, state->maxReordering);
+ EV_DETAIL << "reordering degree updated to " << state->reordering << "\n";
+ }
+ state->rackReordSeen = true; // activate RACK's reordering window as well
+}
+
+void Rfc6675Recovery::onRexmitTimeout()
+{
+ // F-RTO (RFC 5682, SACK-enhanced): open a spurious-RTO detection episode.
+ // Capture the undo context BEFORE the RTO's ssthresh/cwnd reduction (sec 3.2:
+ // recurring timeouts on the same SND.UNA keep the ORIGINAL context, hence the
+ // undoMarker guard), and remember snd_max ("recover") so the episode can be
+ // closed once everything outstanding at the RTO has been accounted for.
+ if (state->frtoEnabled && state->sack_enabled) {
+ if (state->undoMarker == 0)
+ undoInit();
+ state->frtoActive = true;
+ state->frtoHighSeq = state->snd_max;
+ state->frtoOrigAcked = false;
+ }
+ else if (state->lossUndoEnabled && state->undoMarker == 0) {
+ undoInit();
+ }
+}
+
+void Rfc6675Recovery::processFrtoEpisode()
+{
+ if (!state->frtoActive)
+ return;
+ if (state->frtoOrigAcked) {
+ // RFC 5682 step 3.b: never-retransmitted data was (s)acked -- the original
+ // flight arrived, so the RTO was spurious. Restore the pre-RTO cwnd/ssthresh
+ // (Linux tcp_try_undo_loss(frto_undo=true)) and forget the loss marks:
+ // nothing was actually lost.
+ EV_INFO << "F-RTO: spurious retransmission timeout detected, undoing the RTO response\n";
+ undoCwndReduction();
+ conn->getRexmitQueueForUpdate()->resetLostBit();
+ state->afterRto = false;
+ state->rexmit_count = 0; // Linux clears icsk_retransmits on the undo
+ state->frtoActive = false;
+ state->frtoOrigAcked = false;
+ }
+ else if (seqGE(state->snd_una, state->frtoHighSeq)) {
+ // everything outstanding at the RTO has been accounted for through the
+ // conventional recovery: the loss was real, close the episode.
+ state->frtoActive = false;
+ state->undoMarker = 0;
+ }
+}
+
+void Rfc6675Recovery::reoTimeout()
+{
+ // RACK marked further bytes lost while no ACK was arriving. If we are not yet
+ // recovering, this is the fast-retransmit trigger RACK exists to provide;
+ // otherwise just push out whatever the scoreboard now says is missing.
+ if (!state->lossRecovery)
+ step4();
+ else
+ stepC();
+}
+
+void Rfc6675Recovery::segmentsAcked(uint32_t fromSeq, uint32_t toSeq)
+{
+ // F-RTO (RFC 5682 sec 3.1 step 3.b, cumulative side): the scoreboard for
+ // [fromSeq, toSeq) is still intact here. If any part of the newly
+ // cumulatively-acked range was transmitted exactly once -- i.e. is NOT one of
+ // the post-RTO retransmissions -- then the original flight (or part of it)
+ // reached the receiver, so the timeout was spurious.
+ if (state->frtoActive && state->sack_enabled) {
+ auto frq = conn->getRexmitQueue();
+ if (frq != nullptr && frq->getQueueLength() > 0) {
+ for (uint32_t seq = std::max(fromSeq, frq->getBufferStartSeq());
+ seqLess(seq, std::min(toSeq, frq->getBufferEndSeq())); )
+ {
+ const auto& region = frq->getRegion(seq);
+ if (region.transmitCount <= 1) {
+ state->frtoOrigAcked = true;
+ break;
+ }
+ seq = region.endSeqNum;
+ }
+ }
+ }
+ processFrtoEpisode();
+
+ // Adaptive reordering: if this cumulatively-acked segment was never retransmitted
+ // yet sits below already-SACKed data, it was merely reordered (not lost) -- grow the
+ // learned reordering degree so it stops causing spurious fast retransmits.
+ if (state->adaptiveReorderingEnabled && state->sack_enabled) {
+ auto rq = conn->getRexmitQueue();
+ if (rq != nullptr && rq->getQueueLength() > 0
+ && seqLE(rq->getBufferStartSeq(), fromSeq) && seqLess(fromSeq, rq->getBufferEndSeq())
+ && rq->getRegion(fromSeq).transmitCount <= 1)
+ {
+ checkSackReordering(fromSeq);
+ }
+ }
+
+ if (!state->lossUndoEnabled)
+ return;
+
+ // RFC 2883 loss undo. This runs on every ACK that advances snd_una, in or out of
+ // loss recovery -- deliberately not only while recovering, because the D-SACK that
+ // proves a retransmission spurious usually arrives only after the delayed original
+ // has been delivered, by which time the recovery episode has already ended. Linux
+ // likewise checks undo from the ACK path independently of the congestion state.
+ if (state->dsackSeen && state->undoMarker != 0 && state->undoRetrans > 0) {
+ uint32_t segs = (state->dsackBytes + state->snd_mss - 1) / state->snd_mss;
+ state->undoRetrans -= (int32_t)segs;
+ if (state->undoRetrans < 0)
+ state->undoRetrans = 0;
+ }
+
+ if (mayUndo()) {
+ // every retransmission of this episode was D-SACKed: the reduction was
+ // needless, so restore cwnd/ssthresh (and leave recovery if still in it).
+ undoCwndReduction();
+ state->lossRecovery = false;
+ }
+}
+
+void Rfc6675Recovery::dataSent(uint32_t fromSeq)
+{
+ // RFC 6937 accounting: bytes transmitted during the current recovery episode.
+ if (state->prrEnabled && state->lossRecovery && seqGreater(state->snd_nxt, fromSeq))
+ state->prrOut += state->snd_nxt - fromSeq;
+}
+
+void Rfc6675Recovery::segmentRetransmitted(uint32_t fromSeq, uint32_t toSeq)
+{
+ if (state->prrEnabled && state->lossRecovery && seqGreater(toSeq, fromSeq))
+ state->prrOut += toSeq - fromSeq;
+
+ // Eifel (RFC 3522 / Linux retrans_stamp): stamp the FIRST retransmission of the
+ // episode with our TS clock. An ACK later echoing a TSecr OLDER than this was
+ // generated by the ORIGINAL transmission, proving the retransmission spurious.
+ if (state->ts_enabled && state->retransStampTS == 0)
+ state->retransStampTS = TcpConnection::convertSimtimeToTS(simTime());
+
+ // Loss undo: count the retransmissions of this episode that still have to be
+ // proven spurious (Linux increments undo_retrans per retransmitted skb).
+ if (state->lossUndoEnabled && state->undoMarker != 0) {
+ if (state->undoRetrans < 0)
+ state->undoRetrans = 0;
+ uint32_t segs = seqGreater(toSeq, fromSeq)
+ ? (toSeq - fromSeq + state->snd_mss - 1) / state->snd_mss : 1;
+ state->undoRetrans += (int32_t)segs;
+ }
+}
+
+void Rfc6675Recovery::setPipe()
+{
+ ASSERT(state->sack_enabled);
+
+ // RFC 6675, page 3:
+ //"
+ // "HighACK" is the sequence number of the highest byte of data that
+ // has been cumulatively ACKed at a given point.
+ //
+ // "HighData" is the highest sequence number transmitted at a given
+ // point.
+ //
+ // "HighRxt" is the highest sequence number which has been
+ // retransmitted during the current loss recovery phase.
+ //
+ // "RescueRxt" is the highest sequence number which has been
+ // optimistically retransmitted to prevent stalling of the ACK clock
+ // when there is loss at the end of the window and no new data is
+ // available for transmission.
+ //
+ // "Pipe" is a sender's estimate of the number of bytes outstanding
+ // in the network. This is used during recovery for limiting the
+ // sender's sending rate. The pipe variable allows TCP to use a
+ // fundamentally different congestion control than specified in
+ // [RFC5681]. The algorithm is often referred to as the "pipe
+ // algorithm".
+ //
+ // "DupAcks" is the number of duplicate acknowledgments received
+ // since the last cumulative acknowledgment.
+ //"
+ // HighAck = snd_una
+ // HighData = snd_max
+
+ state->highRxt = conn->getRexmitQueue()->getHighestRexmittedSeqNum();
+ state->pipe = 0;
+ uint32_t length = 0;
+
+ // RFC 6675, page 5:
+ //"
+ // This routine traverses the sequence space from HighACK to HighData
+ // and MUST set the "pipe" variable to an estimate of the number of
+ // octets that are currently in transit between the TCP sender and
+ // the TCP receiver. After initializing pipe to zero the following
+ // steps are taken for each octet 'S1' in the sequence space between
+ // HighACK and HighData that has not been SACKed:
+ //"
+ // HighData (snd_max) can sit one sequence number past the last DATA octet
+ // when a FIN is outstanding: the FIN consumes a sequence number but is not
+ // stored in the (data-only) rexmit queue. Scan only the sequence space the
+ // scoreboard actually covers, or checkSackBlock() walks off its end and
+ // aborts (seen on a TFO fallback that closes with data still in flight).
+ //
+ // Walked with the scoreboard iterator rather than by re-searching the queue for
+ // every octet run: checkSackBlock() and (in RACK mode) isLost() each scan from the
+ // front, which made this O(n^2) in the number of regions, on the hottest path there
+ // is. Each iteration still covers exactly one region-suffix, so the arithmetic and
+ // the order of the two increments below are unchanged.
+ //
+ auto *rexmitQueue = conn->getRexmitQueue();
+ uint32_t scanEnd = rexmitQueue->getBufferEndSeq();
+ if (seqLess(state->snd_max, scanEnd))
+ scanEnd = state->snd_max;
+ auto region = rexmitQueue->rexmitQueue.begin();
+ for (uint32_t s1 = state->snd_una; seqLess(s1, scanEnd); s1 += length) {
+ while (region != rexmitQueue->rexmitQueue.end() && seqLE(region->endSeqNum, s1))
+ region++;
+ ASSERT(region != rexmitQueue->rexmitQueue.end());
+ length = region->endSeqNum - s1;
+ bool sacked = region->sacked;
+
+ if (!sacked) {
+ // RFC 6675, page 5:
+ //"
+ // (a) If IsLost (S1) returns false:
+ //
+ // Pipe is incremented by 1 octet.
+ //
+ // The effect of this condition is that pipe is incremented for
+ // packets that have not been SACKed and have not been determined
+ // to have been lost (i.e., those segments that are still assumed
+ // to be in the network).
+ //"
+ // in RACK mode isLost() is exactly this region's lost flag (its
+ // out-of-range guards cannot fire here: snd_una <= s1 < scanEnd)
+ bool lost = state->lossDetectionMode == 1 ? region->lost : isLost(s1);
+ if (lost == false)
+ state->pipe += length;
+
+ // RFC 6675, pages 5:
+ //"
+ // (b) If S1 <= HighRxt:
+ //
+ // Pipe is incremented by 1 octet.
+ //
+ // The effect of this condition is that pipe is incremented for
+ // the retransmission of the octet.
+ //
+ // Note that octets retransmitted without being considered lost are
+ // counted twice by the above mechanism.
+ //"
+ if (seqLess(s1, state->highRxt))
+ state->pipe += length;
+ }
+ }
+
+ conn->emit(pipeSignal, state->pipe);
+}
+
+bool Rfc6675Recovery::nextSeg(uint32_t& seqNum)
+{
+ ASSERT(state->sack_enabled);
+
+ // RFC 6675, page 6:
+ //"
+ // This routine uses the scoreboard data structure maintained by the
+ // Update() function to determine what to transmit based on the SACK
+ // information that has arrived from the data receiver (and hence
+ // been marked in the scoreboard). NextSeg () MUST return the
+ // sequence number range of the next segment that is to be
+ // transmitted, per the following rules:
+ //"
+
+ state->highRxt = conn->getRexmitQueue()->getHighestRexmittedSeqNum();
+ uint32_t highestSackedSeqNum = conn->getRexmitQueue()->getHighestSackedSeqNum();
+ uint32_t shift = state->snd_mss;
+ bool sacked = false; // required for rexmitQueue->checkSackBlock()
+ bool rexmitted = false; // required for rexmitQueue->checkSackBlock()
+
+ seqNum = 0;
+
+ if (state->ts_enabled)
+ shift -= B(TCP_OPTION_TS_SIZE).get();
+
+ // RFC 6675, page 6:
+ //"
+ // (1) If there exists a smallest unSACKed sequence number 'S2' that
+ // meets the following three criteria for determining loss, the
+ // sequence range of one segment of up to SMSS octets starting
+ // with S2 MUST be returned.
+ //
+ // (1.a) S2 is greater than HighRxt.
+ //
+ // (1.b) S2 is less than the highest octet covered by any
+ // received SACK.
+ //
+ // (1.c) IsLost (S2) returns true.
+ //"
+
+ // RACK mode: Linux tcp_xmit_retransmit_queue walks the whole rtx queue by
+ // sequence with no HighRxt floor -- it skips SACKED_RETRANS entries and
+ // (re)transmits anything marked LOST. That reaches a lost region BELOW the
+ // highest retransmission whose rexmitted flag RACK just cleared (its first
+ // retransmit died).
+ // Rule (1.a)'s "S2 greater than HighRxt" would hide it forever.
+ if (state->lossDetectionMode == 1) {
+ for (const auto& region : conn->getRexmitQueue()->rexmitQueue) {
+ if (!seqLess(region.beginSeqNum, highestSackedSeqNum))
+ break;
+ if (!region.sacked && region.lost && !region.rexmitted) {
+ seqNum = region.beginSeqNum;
+ return true;
+ }
+ }
+ }
+ else
+ // Note: state->highRxt == RFC.HighRxt + 1
+ for (uint32_t s2 = state->highRxt;
+ seqLess(s2, state->snd_max) && seqLess(s2, highestSackedSeqNum);
+ s2 += shift)
+ {
+ conn->getRexmitQueue()->checkSackBlock(s2, shift, sacked, rexmitted);
+
+ if (!sacked) {
+ if (isLost(s2)) { // 1.a and 1.b are true, see above "for" statement
+ seqNum = s2;
+
+ return true;
+ }
+
+ break; // !isLost(x) --> !isLost(x + d)
+ }
+ }
+
+ // RFC 6675, page 6
+ //"
+ // (2) If no sequence number 'S2' per rule (1) exists but there
+ // exists available unsent data and the receiver's advertised
+ // window allows, the sequence range of one segment of up to SMSS
+ // octets of previously unsent data starting with sequence number
+ // HighData+1 MUST be returned.
+ //"
+ {
+ // check how many unsent bytes we have
+ uint32_t buffered = conn->getSendQueue()->getBytesAvailable(state->snd_max);
+ uint32_t maxWindow = state->snd_wnd;
+ // effectiveWindow: number of bytes we're allowed to send now. pipe may exceed
+ // the advertised window (RFC 6675 setPipe counts retransmitted-not-lost octets
+ // twice, and snd_wnd can shrink), so the difference must not wrap.
+ uint32_t effectiveWin = maxWindow > state->pipe ? maxWindow - state->pipe : 0;
+
+ if (buffered > 0 && effectiveWin >= state->snd_mss) {
+ seqNum = state->snd_max; // HighData = snd_max
+
+ return true;
+ }
+ }
+
+ // RFC 6675, pages 6 and 7
+ //"
+ // (3) If the conditions for rules (1) and (2) fail, but there exists
+ // an unSACKed sequence number 'S3' that meets the criteria for
+ // detecting loss given in steps (1.a) and (1.b) above
+ // (specifically excluding step (1.c)) then one segment of up to
+ // SMSS octets starting with S3 MAY be returned.
+ //
+ // (4) If the conditions for (1), (2), and (3) fail, but there exists
+ // outstanding unSACKed data, we provide the opportunity for a
+ // single "rescue" retransmission per entry into loss recovery.
+ // If HighACK is greater than RescueRxt (or RescueRxt is
+ // undefined), then one segment of up to SMSS octets that MUST
+ // include the highest outstanding unSACKed sequence number
+ // SHOULD be returned, and RescueRxt set to RecoveryPoint.
+ // HighRxt MUST NOT be updated.
+ //
+ // Note that rule (3) and (4) are a sort of retransmission "last resort".
+ // They allow for retransmission of sequence numbers even when the
+ // sender has less certainty a segment has been lost than as with
+ // rule (1). Retransmitting segments via rule (3) and (4) will help
+ // sustain TCP's ACK clock and therefore can potentially help
+ // avoid retransmission timeouts. However, in sending these
+ // segments, the sender has two copies of the same data considered
+ // to be in the network (and also in the Pipe estimate, in the case of (3)). When an
+ // ACK or SACK arrives covering this retransmitted segment, the
+ // sender cannot be sure exactly how much data left the network
+ // (one of the two transmissions of the packet or both
+ // transmissions of the packet). Therefore the sender may
+ // underestimate Pipe by considering both segments to have left
+ // the network when it is possible that only one of the two has.
+ //"
+ // TODO: rule 4 clause
+ {
+ for (uint32_t s3 = state->highRxt;
+ seqLess(s3, state->snd_max) && seqLess(s3, highestSackedSeqNum);
+ s3 += shift)
+ {
+ conn->getRexmitQueue()->checkSackBlock(s3, shift, sacked, rexmitted);
+
+ if (!sacked) {
+ // 1.a and 1.b are true, see above "for" statement
+ seqNum = s3;
+
+ return true;
+ }
+ }
+ }
+
+ // RFC 6675, page 7:
+ //"
+ // (5) If the conditions for each of (1), (2), (3), and (4) are not
+ // met, then NextSeg () MUST indicate failure, and no segment is
+ // returned.
+ //"
+ seqNum = 0;
+
+ return false;
+}
+
+void Rfc6675Recovery::sendDataDuringLossRecoveryPhase(uint32_t congestionWindow)
+{
+ ASSERT(state->sack_enabled && state->lossRecovery);
+
+ // RFC 6675, page 9
+ //"
+ // (4.5) In order to take advantage of potential additional available
+ // cwnd, proceed to step (C) below.
+ // (...)
+ // (C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more
+ // segments as follows:
+ // (...)
+ // (C.5) If cwnd - pipe >= 1 SMSS, return to (C.1)
+ //"
+ while (((int)congestionWindow - (int)state->pipe) >= (int)state->snd_mss) { // Note: Typecast needed to avoid prohibited transmissions
+ // RFC 6675, page 9:
+ //"
+ // (C.1) The scoreboard MUST be queried via NextSeg () for the
+ // sequence number range of the next segment to transmit (if any),
+ // and the given segment sent. If NextSeg () returns failure (no
+ // data to send) return without sending anything (i.e., terminate
+ // steps C.1 -- C.5).
+ //"
+
+ uint32_t seqNum;
+
+ if (!nextSeg(seqNum)) // if nextSeg() returns false (=failure): terminate steps C.1 -- C.5
+ break;
+
+ uint32_t sentBytes = sendSegmentDuringLossRecoveryPhase(seqNum);
+ // RFC 6675, page 9:
+ //"
+ // (C.4) The estimate of the amount of data outstanding in the
+ // network must be updated by incrementing pipe by the number of
+ // octets transmitted in (C.1).
+ //"
+ state->pipe += sentBytes;
+ }
+}
+
+uint32_t Rfc6675Recovery::sendSegmentDuringLossRecoveryPhase(uint32_t seqNum)
+{
+ ASSERT(state->sack_enabled && state->lossRecovery);
+
+ // start sending from seqNum
+ state->snd_nxt = seqNum;
+
+ uint32_t old_highRxt = conn->getRexmitQueue()->getHighestRexmittedSeqNum();
+
+ // no need to check cwnd and rwnd - has already be done before
+ // no need to check nagle - sending mss bytes
+ uint32_t sentBytes = conn->sendSegment(state->snd_mss);
+
+ uint32_t sentSeqNum = seqNum + sentBytes;
+
+ if (state->send_fin && sentSeqNum == state->snd_fin_seq)
+ sentSeqNum = sentSeqNum + 1;
+
+ ASSERT(seqLE(state->snd_nxt, sentSeqNum));
+
+ // RFC 6675, page 9:
+ //"
+ // (C.2) If any of the data octets sent in (C.1) are below HighData,
+ // HighRxt MUST be set to the highest sequence number of the
+ // retransmitted segment unless NextSeg () rule (4) was
+ // invoked for this retransmission.
+ //"
+ // TODO: rule 4 clause
+ if (seqLess(seqNum, state->snd_max)) { // HighData = snd_max
+ state->highRxt = conn->getRexmitQueue()->getHighestRexmittedSeqNum();
+ }
+
+ // RFC 6675, page 9:
+ //"
+ // (C.3) If any of the data octets sent in (C.1) are above HighData,
+ // HighData must be updated to reflect the transmission of
+ // previously unsent data.
+ //"
+ if (seqGreater(sentSeqNum, state->snd_max)) { // HighData = snd_max
+ state->snd_max = sentSeqNum;
+ conn->emit(sndMaxSignal, state->snd_max);
+ }
+
+ conn->emit(unackedSignal, state->snd_max - state->snd_una);
+
+ // RFC 6675, page 11:
+ //"
+ // 6 Managing the RTO Timer
+ //
+ // The standard TCP RTO estimator is defined in [RFC6288]. Due to the
+ // fact that the SACK algorithm in this document can have an impact on
+ // the behavior of the estimator, implementers may wish to consider how
+ // the timer is managed. [RFC6288] calls for the RTO timer to be
+ // re-armed each time an ACK arrives that advances the cumulative ACK
+ // point. Because the algorithm presented in this document can keep the
+ // ACK clock going through a fairly significant loss event,
+ // (comparatively longer than the algorithm described in [RFC5681]), on
+ // some networks the loss event could last longer than the RTO. In this
+ // case the RTO timer would expire prematurely and a segment that need
+ // not be retransmitted would be resent.
+ //
+ // Therefore we give implementers the latitude to use the standard
+ // [RFC6288] style RTO management or, optionally, a more careful variant
+ // that re-arms the RTO timer on each retransmission that is sent during
+ // recovery MAY be used. This provides a more conservative timer than
+ // specified in [RFC6288], and so may not always be an attractive
+ // alternative. However, in some cases it may prevent needless
+ // retransmissions, go-back-N transmission and further reduction of the
+ // congestion window.
+ //"
+ conn->getTcpAlgorithmForUpdate()->ackSent();
+
+ if (old_highRxt != state->highRxt) {
+ // Note: Restart of REXMIT timer on retransmission is not part of RFC 5681, however optional in RFC 6675 if sent during recovery.
+ EV_INFO << "Retransmission sent during recovery, restarting REXMIT timer.\n";
+ conn->getTcpAlgorithmForUpdate()->restartRexmitTimer();
+ }
+ else // don't measure RTT for retransmitted packets
+ conn->getTcpAlgorithmForUpdate()->dataSent(seqNum); // seqNum = old_snd_nxt
+
+ return sentBytes;
+}
+
+TcpHeader Rfc6675Recovery::addSacks(const Ptr& tcpHeader)
+{
+ B options_len = B(0);
+ B used_options_len = tcpHeader->getHeaderOptionArrayLength();
+ bool dsack_inserted = false; // set if dsack is subsets of a bigger sack block recently reported
+
+ uint32_t start = state->start_seqno;
+ uint32_t end = state->end_seqno;
+
+ // delete old sacks (below rcv_nxt), delete duplicates and print previous status of sacks_array:
+ auto it = state->sacks_array.begin();
+ EV_INFO << "Previous status of sacks_array: \n" << ((it != state->sacks_array.end()) ? "" : "\t EMPTY\n");
+
+ while (it != state->sacks_array.end()) {
+ if (seqLE(it->getEnd(), state->rcv_nxt) || it->empty()) {
+ EV_DETAIL << "\t SACK in sacks_array: " << " " << it->str() << " delete now\n";
+ it = state->sacks_array.erase(it);
+ }
+ else {
+ EV_DETAIL << "\t SACK in sacks_array: " << " " << it->str() << endl;
+
+ ASSERT(seqGE(it->getStart(), state->rcv_nxt));
+
+ it++;
+ }
+ }
+
+ if (used_options_len > TCP_OPTIONS_MAX_SIZE - TCP_OPTION_SACK_MIN_SIZE) {
+ EV_ERROR << "ERROR: Failed to addSacks - at least 10 free bytes needed for SACK - used_options_len=" << used_options_len << endl;
+
+ // reset flags:
+ state->snd_sack = false;
+ state->snd_dsack = false;
+ state->start_seqno = 0;
+ state->end_seqno = 0;
+ return *tcpHeader;
+ }
+
+ if (start != end) {
+ if (state->dsack_enabled && state->snd_dsack) { // SequenceNo < rcv_nxt
+ // RFC 2883, page 3:
+ //"
+ // (3) The left edge of the D-SACK block specifies the first sequence
+ // number of the duplicate contiguous sequence, and the right edge of
+ // the D-SACK block specifies the sequence number immediately following
+ // the last sequence in the duplicate contiguous sequence.
+ //"
+ if (seqLess(start, state->rcv_nxt) && seqLess(state->rcv_nxt, end))
+ end = state->rcv_nxt;
+
+ dsack_inserted = true;
+ Sack nSack(start, end);
+ state->sacks_array.push_front(nSack);
+ EV_DETAIL << "inserted DSACK entry: " << nSack.str() << "\n";
+ }
+ else if (seqGreater(end, state->rcv_nxt)) {
+ uint32_t contStart = conn->getReceiveQueue()->getLE(start);
+ uint32_t contEnd = conn->getReceiveQueue()->getRE(end);
+
+ Sack newSack(contStart, contEnd);
+ state->sacks_array.push_front(newSack);
+ EV_DETAIL << "Inserted SACK entry: " << newSack.str() << "\n";
+ }
+
+ // RFC 2883, page 3:
+ //"
+ // (3) The left edge of the D-SACK block specifies the first sequence
+ // number of the duplicate contiguous sequence, and the right edge of
+ // the D-SACK block specifies the sequence number immediately following
+ // the last sequence in the duplicate contiguous sequence."
+
+ // RFC 2018, page 4:
+ // "* The first SACK block (i.e., the one immediately following the
+ // kind and length fields in the option) MUST specify the contiguous
+ // block of data containing the segment which triggered this ACK,
+ // unless that segment advanced the Acknowledgment Number field in
+ // the header. This assures that the ACK with the SACK option
+ // reflects the most recent change in the data receiver's buffer
+ // queue.
+ //"
+
+ // RFC 2018, page 4:
+ //"
+ // * The first SACK block (i.e., the one immediately following the
+ // kind and length fields in the option) MUST specify the contiguous
+ // block of data containing the segment which triggered this ACK,
+ //"
+
+ // RFC 2883, page 3:
+ // (4) If the D-SACK block reports a duplicate contiguous sequence from
+ // a (possibly larger) block of data in the receiver's data queue above
+ // the cumulative acknowledgement, then the second SACK block in that
+ // SACK option should specify that (possibly larger) block of data.
+ //
+ // (5) Following the SACK blocks described above for reporting duplicate
+ // segments, additional SACK blocks can be used for reporting additional
+ // blocks of data, as specified in RFC 2018.
+ //"
+
+ // RFC 2018, page 4:
+ // * The SACK option SHOULD be filled out by repeating the most
+ // recently reported SACK blocks (based on first SACK blocks in
+ // previous SACK options) that are not subsets of a SACK block
+ // already included in the SACK option being constructed.
+ //"
+
+ it = state->sacks_array.begin();
+ if (dsack_inserted)
+ it++;
+
+ for (; it != state->sacks_array.end(); it++) {
+ ASSERT(!it->empty());
+
+ auto it2 = it;
+ it2++;
+ while (it2 != state->sacks_array.end()) {
+ if (it->contains(*it2)) {
+ EV_DETAIL << "sack matched, delete contained : a=" << it->str() << ", b=" << it2->str() << endl;
+ it2 = state->sacks_array.erase(it2);
+ }
+ else
+ it2++;
+ }
+ }
+ }
+
+ uint n = state->sacks_array.size();
+
+ uint maxnode = ((B(TCP_OPTIONS_MAX_SIZE - used_options_len).get()) - 2) / 8; // 2: option header, 8: size of one sack entry
+
+ // Linux tcp_options_fit_accecn's SACK-reduction arm: when an AccECN option
+ // is REQUIRED (a counter changed since the last one, accEcnOptMinFields>0),
+ // give up SACK blocks -- but never below 2 -- so the option fits at the
+ // kernel's canonical padding (nop,nop,TS = 12B; nop,nop,SACK = 4+8n). If
+ // even 2 blocks plus the required fields don't fit, keep all blocks and
+ // the option is omitted instead (sack_space_grab pins both directions:
+ // the CE reply drops to 2 blocks + 2 fields, the ECT0 reply keeps 3
+ // blocks and no option).
+ if (state->accEcnNegotiated && state->accEcnOptionEnabled && state->sawAccEcnOpt
+ && state->accEcnOptMinFields > 0 && n > 2)
+ {
+ uint32_t canonical = state->ts_enabled ? 12 : 0;
+ uint32_t optAlign = ((2 + 3 * (uint32_t)state->accEcnOptMinFields) + 3) & ~3u;
+ uint32_t maxBlocks = n;
+ while (maxBlocks > 2 && canonical + (4 + 8 * maxBlocks) + optAlign > 40)
+ maxBlocks--;
+ if (canonical + (4 + 8 * maxBlocks) + optAlign <= 40 && maxBlocks < n)
+ n = maxBlocks;
+ }
+
+ if (n > maxnode)
+ n = maxnode;
+
+ if (n == 0) {
+ if (dsack_inserted)
+ state->sacks_array.pop_front(); // delete DSACK entry
+
+ // reset flags:
+ state->snd_sack = false;
+ state->snd_dsack = false;
+ state->start_seqno = 0;
+ state->end_seqno = 0;
+
+ return *tcpHeader;
+ }
+
+ while (B(used_options_len).get() % 4 != 2)
+ used_options_len++;
+
+ ASSERT(B(used_options_len).get() % 4 == 2);
+
+ TcpOptionSack *option = new TcpOptionSack();
+ option->setLength(8 * n + 2);
+ option->setSackItemArraySize(n);
+
+ // write sacks from sacks_array to options
+ uint counter = 0;
+
+ for (it = state->sacks_array.begin(); it != state->sacks_array.end() && counter < n; it++) {
+ ASSERT(it->getStart() != it->getEnd());
+ option->setSackItem(counter++, *it);
+ }
+
+ // independent of "n" we always need 2 padding bytes (NOP) to make: (used_options_len % 4 == 0)
+ options_len = used_options_len + TCP_OPTION_SACK_ENTRY_SIZE * n + TCP_OPTION_HEAD_SIZE; // 8 bytes for each SACK (n) + 2 bytes for kind&length
+
+ ASSERT(options_len <= TCP_OPTIONS_MAX_SIZE); // Options length allowed? - maximum: 40 Bytes
+
+ tcpHeader->appendHeaderOption(option);
+ tcpHeader->setHeaderLength(TCP_MIN_HEADER_LENGTH + tcpHeader->getHeaderOptionArrayLength());
+ tcpHeader->setChunkLength(tcpHeader->getHeaderLength());
+ // update number of sent sacks
+ state->snd_sacks += n;
+
+ conn->emit(sndSacksSignal, state->snd_sacks);
+
+ EV_INFO << n << " SACK(s) added to header:\n";
+
+ for (uint t = 0; t < n; t++) {
+ EV_INFO << t << ". SACK:" << " [" << option->getSackItem(t).getStart() << ".." << option->getSackItem(t).getEnd() << ")";
+
+ if (t == 0) {
+ if (state->snd_dsack)
+ EV_INFO << " (D-SACK)";
+ else if (seqLE(option->getSackItem(t).getEnd(), state->rcv_nxt)) {
+ EV_INFO << " (received segment filled out a gap)";
+ state->snd_dsack = true; // Note: Set snd_dsack to delete first sack from sacks_array
+ }
+ }
+
+ EV_INFO << endl;
+ }
+
+ // RFC 2883, page 3:
+ //"
+ // (1) A D-SACK block is only used to report a duplicate contiguous
+ // sequence of data received by the receiver in the most recent packet.
+ //
+ // (2) Each duplicate contiguous sequence of data received is reported
+ // in at most one D-SACK block. (I.e., the receiver sends two identical
+ // D-SACK blocks in subsequent packets only if the receiver receives two
+ // duplicate segments.)
+ //
+ // In case of d-sack: delete first sack (d-sack) and move old sacks by one to the left
+ //"
+ if (dsack_inserted)
+ state->sacks_array.pop_front(); // delete DSACK entry
+
+ // reset flags:
+ state->snd_sack = false;
+ state->snd_dsack = false;
+ state->start_seqno = 0;
+ state->end_seqno = 0;
+
+ return *tcpHeader;
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.h b/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.h
new file mode 100644
index 00000000000..c3274f00648
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/Rfc6675Recovery.h
@@ -0,0 +1,136 @@
+//
+// Copyright (C) 2020 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_RFC6675RECOVERY_H
+#define __INET_RFC6675RECOVERY_H
+
+#include "inet/transportlayer/tcp/ITcpRecovery.h"
+#include "inet/transportlayer/tcp/TcpConnection.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Implements RFC 6675: A Conservative Loss Recovery Algorithm Based on Selective Acknowledgment (SACK) for TCP.
+ */
+class INET_API Rfc6675Recovery : public ITcpRecovery
+{
+ protected:
+ TcpClassicAlgorithmBaseStateVariables *state = nullptr;
+ TcpConnection *conn = nullptr;
+
+ virtual void stepA();
+ virtual void stepB();
+ virtual void stepC();
+
+ virtual void step4();
+
+ public:
+ Rfc6675Recovery(TcpStateVariables *state, TcpConnection *conn) : state(check_and_cast(state)), conn(conn) { }
+
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ virtual void receivedAckForUnackedData(uint32_t numBytesAcked) override;
+
+ virtual void receivedDuplicateAck() override;
+
+ virtual bool processSACKOption(const Ptr& tcpHeader, const TcpOptionSack& option);
+ /**
+ * For SACK TCP. RFC 6675, page 5: "This routine returns whether the given
+ * sequence number is considered to be lost. The routine returns true when
+ * either DupThresh discontiguous SACKed sequences have arrived above
+ * 'SeqNum' or (DupThresh - 1) * SMSS bytes with sequence numbers greater
+ * than 'SeqNum' have been SACKed. Otherwise, the routine returns
+ * false."
+ */
+ virtual bool isLost(uint32_t seqNum);
+
+ /**
+ * RFC 8985 RACK: advance the RACK reference to the most recently sent
+ * delivered segment and mark earlier-sent, still-unacked segments as lost
+ * once RACK.rtt + reo_wnd has elapsed. Returns the number of newly lost bytes.
+ */
+ virtual uint32_t rackDetectAndMarkLost(bool fromReoTimer = false);
+
+ /** @name Proportional Rate Reduction (RFC 6937), Linux tcp_cwnd_reduction() */
+ //@{
+ /** Newly acked+sacked bytes carried by the ACK currently being processed. */
+ virtual uint32_t prrNewlyDelivered() const;
+ /** Per-ACK cwnd sizing: snd_cwnd = pipe + sndcnt. */
+ virtual void prrCwndReduction(int newlyAckedSacked, int newlyLost, bool sndUnaAdvanced);
+ /** Recovery exit: snd_cwnd = ssthresh. */
+ virtual void prrEndCwndReduction();
+ //@}
+
+ /** @name Loss undo (RFC 2883 D-SACK, RFC 3522 Eifel), Linux tcp_undo_cwnd_reduction() */
+ //@{
+ /** Capture the undo context (marker, priorCwnd/priorSsthresh) at recovery entry. */
+ virtual void undoInit();
+ /** True if the cwnd reduction of the current episode may be undone. */
+ /** Eifel (RFC 3522): the last ACK's TSecr predates our first retransmission. */
+ virtual bool packetDelayed() const;
+ virtual bool mayUndo() const;
+ /** Restore cwnd/ssthresh reduced by a now-known-spurious recovery. */
+ virtual void undoCwndReduction();
+ //@}
+
+ /**
+ * Linux tcp_check_sack_reordering(): reordering is proven when data at lowSeq
+ * was delivered while a higher sequence number (the SACK fack) had already been
+ * SACKed. Grows the learned reordering degree, bounded by maxReordering.
+ */
+ virtual void checkSackReordering(uint32_t lowSeq);
+
+ /** RFC 5682 F-RTO: decide/close a spurious-RTO episode. */
+ virtual void processFrtoEpisode();
+
+ virtual void onRexmitTimeout() override;
+ virtual void reoTimeout() override;
+ virtual void segmentsAcked(uint32_t fromSeq, uint32_t toSeq) override;
+ virtual void dataSent(uint32_t fromSeq) override;
+ virtual void segmentRetransmitted(uint32_t fromSeq, uint32_t toSeq) override;
+
+ /**
+ * For SACK TCP. RFC 6675, page 5: "This routine traverses the sequence
+ * space from HighACK to HighData and MUST set the "pipe" variable to an
+ * estimate of the number of octets that are currently in transit between
+ * the TCP sender and the TCP receiver."
+ */
+ virtual void setPipe();
+
+ /**
+ * For SACK TCP. RFC 6675, page 6: "This routine uses the scoreboard data
+ * structure maintained by the Update() function to determine what to transmit
+ * based on the SACK information that has arrived from the data receiver
+ * (and hence been marked in the scoreboard). NextSeg () MUST return the
+ * sequence number range of the next segment that is to be
+ * transmitted..."
+ * Returns true if a valid sequence number (for the next segment) is found and
+ * returns false if no segment should be send.
+ */
+ virtual bool nextSeg(uint32_t& seqNum);
+
+ /**
+ * Utility: send data during Loss Recovery phase (if SACK is enabled).
+ */
+ virtual void sendDataDuringLossRecoveryPhase(uint32_t congestionWindow);
+
+ /**
+ * Utility: send segment during Loss Recovery phase (if SACK is enabled).
+ * Returns the number of bytes sent.
+ */
+ virtual uint32_t sendSegmentDuringLossRecoveryPhase(uint32_t seqNum);
+
+ /** Utility: adds SACKs to segments header options field */
+ virtual TcpHeader addSacks(const Ptr& tcpHeader);
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.cc b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.cc
new file mode 100644
index 00000000000..260a3afe768
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.cc
@@ -0,0 +1,1107 @@
+//
+// Copyright (C) 2004 OpenSim Ltd.
+// Copyright (C) 2009-2010 Thomas Reschka
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
+
+#include "inet/transportlayer/tcp/Tcp.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+#include "inet/transportlayer/tcp/TcpSendQueue.h"
+
+namespace inet {
+namespace tcp {
+
+// RFC 1122, page 95:
+// "A TCP SHOULD implement a delayed ACK, but an ACK should not
+// be excessively delayed; in particular, the delay MUST be
+// less than 0.5 seconds, and in a stream of full-sized
+// segments there SHOULD be an ACK for at least every second
+// segment."
+
+std::string TcpAlgorithmBaseStateVariables::str() const
+// Linux-shaped adaptive receiver ACK dynamics (adaptiveDelayedAcks parameter);
+// values are Linux's long-stable ABI constants (TCP_ATO_MIN/TCP_DELACK_MIN =
+// HZ/25, TCP_DELACK_MAX = HZ/5, TCP_MAX_QUICKACKS, TCP_PINGPONG_THRESH).
+#define TCP_ATO_MIN_S 0.04 // 40ms: ATO floor and quickack-mode ATO
+#define TCP_DELACK_MIN_S 0.04 // 40ms
+#define TCP_DELACK_MAX_S 0.2 // 200ms
+#define TCP_MAX_QUICKACKS 16
+#define TCP_PINGPONG_THRESH 1 // Linux sysctl_tcp_pingpong_thresh default (tcp_ipv4.c)
+{
+ std::stringstream out;
+ out << TcpStateVariables::str();
+ out << " snd_cwnd=" << snd_cwnd;
+ out << " rto=" << rexmit_timeout;
+ return out.str();
+}
+
+std::string TcpAlgorithmBaseStateVariables::detailedInfo() const
+{
+ std::stringstream out;
+ out << TcpStateVariables::detailedInfo();
+ out << "snd_cwnd=" << snd_cwnd << "\n";
+ out << "rto=" << rexmit_timeout << "\n";
+ out << "persist_timeout=" << persist_timeout << "\n";
+ // TODO add others too
+ return out.str();
+}
+
+TcpAlgorithmBase::TcpAlgorithmBase() : TcpAlgorithm(),
+ state((TcpAlgorithmBaseStateVariables *&)TcpAlgorithm::state)
+{
+ rexmitTimer = persistTimer = delayedAckTimer = keepAliveTimer = tlpTimer = corkTimer = nullptr;
+}
+
+TcpAlgorithmBase::~TcpAlgorithmBase()
+{
+ // Note: don't delete "state" here, it'll be deleted from TcpConnection
+ // cancel and delete timers
+ if (rexmitTimer)
+ delete cancelEvent(rexmitTimer);
+ if (persistTimer)
+ delete cancelEvent(persistTimer);
+ if (delayedAckTimer)
+ delete cancelEvent(delayedAckTimer);
+ if (keepAliveTimer)
+ delete cancelEvent(keepAliveTimer);
+ if (tlpTimer)
+ delete cancelEvent(tlpTimer);
+ if (corkTimer)
+ delete cancelEvent(corkTimer);
+}
+
+void TcpAlgorithmBase::initialize()
+{
+ TcpAlgorithm::initialize();
+
+ rexmitTimer = new cMessage("REXMIT");
+ persistTimer = new cMessage("PERSIST");
+ delayedAckTimer = new cMessage("DELAYEDACK");
+ keepAliveTimer = new cMessage("KEEPALIVE");
+ tlpTimer = new cMessage("TLP-PTO");
+ // schedulePto() caps the probe at the RTO's remaining time, so the two
+ // timers can land on the very same instant; Linux keeps them in ONE icsk
+ // slot where an armed probe REPLACES the RTO. Give the probe the earlier
+ // position on ties -- its handler then pushes the RTO out by a full
+ // period (rearm in processPtoTimer), while a failed/skipped probe leaves
+ // the same-instant RTO to fire right after, so no deadlock is possible.
+ tlpTimer->setSchedulingPriority(-1);
+ corkTimer = new cMessage("CORK");
+
+ rexmitTimer->setContextPointer(conn);
+ persistTimer->setContextPointer(conn);
+ delayedAckTimer->setContextPointer(conn);
+ keepAliveTimer->setContextPointer(conn);
+ tlpTimer->setContextPointer(conn);
+ corkTimer->setContextPointer(conn);
+
+ state->keepalive_enabled = conn->getTcpMain()->par("keepAliveEnabled");
+ state->keepalive_idle_time = conn->getTcpMain()->par("keepAliveIdleTime");
+ state->keepalive_interval = conn->getTcpMain()->par("keepAliveInterval");
+ state->keepalive_max_probes = conn->getTcpMain()->par("keepAliveProbeCount");
+
+ state->rexmit_timeout = initialRto;
+}
+
+uint32_t TcpAlgorithmBase::initialWindow() const
+{
+ // A route's initcwnd outranks the RFC default, as it does in Linux
+ // (dst_metric(RTAX_INITCWND) wins over TCP_INIT_CWND in tcp_init_cwnd).
+ if (state->initialCwndSegments > 0)
+ return state->initialCwndSegments * state->snd_effmss;
+
+ switch (state->init_cwnd_mode) {
+ case 1: // RFC 3390
+ return std::min(4 * state->snd_effmss, std::max(2 * state->snd_effmss, (uint32_t)4380));
+ case 2: // RFC 6928 (IW10)
+ return std::min(10 * state->snd_effmss, std::max(2 * state->snd_effmss, (uint32_t)14600));
+ default: // RFC 2001: one segment
+ return state->snd_effmss;
+ }
+}
+
+void TcpAlgorithmBase::established(bool active)
+{
+ // Linux seeds icsk_ack.lrcvtime at connection establishment
+ // (tcp_finish_connect / openreq child init), NOT at the first data
+ // arrival: a data segment sent within one ATO of the handshake already
+ // counts as interactive ("pingpong") evidence, suppressing the quickack
+ // that would otherwise ACK the peer's reply immediately
+ // (fastopen cookie-less-sendto pins the reply data being ACKed only by
+ // the subsequent close()'s FIN).
+ state->lastDataRecvTime = simTime();
+
+ // Linux tcp_rcv_synsent_state_process, write-pending arm: when data is
+ // already queued behind the handshake (a TFO remainder, a deferred
+ // send), the bare third ACK is saved ("data will be ready after several
+ // ticks") and tcp_enter_quickack_mode() runs -- seeding the ATO, so the
+ // data leaving this very instant registers as pingpong evidence in
+ // dataSent(). The peer's first reply is then ACKed on the DELAYED path
+ // (cookie-less-sendto: reply data acked only by the close()'s FIN).
+ if (active && state->adaptiveDelayedAcks
+ && conn->getSendQueue()->getBytesAvailable(state->snd_nxt) > 0)
+ enterQuickackMode(TCP_MAX_QUICKACKS);
+
+ // "Prevent spurious tcp_cwnd_restart() on first data" (tcp_finish_connect):
+ // a slow handshake (e.g. a retransmitted TFO SYN, +1s) must not count as
+ // idle time -- without this, the after-idle restart clamps cwnd right when
+ // the unacknowledged SYN data is being retransmitted and the send stalls
+ // (cookie-less-sendto's non-blocking test pins P. 1:1001 leaving WITH the
+ // handshake ACK).
+ state->time_last_data_sent = simTime();
+
+ // initialize cwnd (we may learn SMSS during connection setup)
+
+ // RFC 3390, page 2: "The upper bound for the initial window is given more precisely in
+ // (1):
+ //
+ // min (4*MSS, max (2*MSS, 4380 bytes)) (1)
+ //
+ // Note: Sending a 1500 byte packet indicates a maximum segment size
+ // (MSS) of 1460 bytes (assuming no IP or TCP options). Therefore,
+ // limiting the initial window's MSS to 4380 bytes allows the sender to
+ // transmit three segments initially in the common case when using 1500
+ // byte packets.
+ //
+ // Equivalently, the upper bound for the initial window size is based on
+ // the MSS, as follows:
+ //
+ // If (MSS <= 1095 bytes)
+ // then win <= 4 * MSS;
+ // If (1095 bytes < MSS < 2190 bytes)
+ // then win <= 4380;
+ // If (2190 bytes <= MSS)
+ // then win <= 2 * MSS;
+ //
+ // This increased initial window is optional: a TCP MAY start with a
+ // larger initial window. However, we expect that most general-purpose
+ // TCP implementations would choose to use the larger initial congestion
+ // window given in equation (1) above.
+ //
+ // This upper bound for the initial window size represents a change from
+ // RFC 2581 [RFC 2581], which specified that the congestion window be
+ // initialized to one or two segments.
+ // (...)
+ // If the SYN or SYN/ACK is
+ // lost, the initial window used by a sender after a correctly
+ // transmitted SYN MUST be one segment consisting of MSS bytes."
+ // RFC 3390/6928: if the SYN or SYN/ACK was lost, the initial window is 1 SMSS.
+ if (state->syn_rexmit_count == 0) {
+ state->snd_cwnd = initialWindow();
+ if (state->init_cwnd_mode != 0)
+ EV_DETAIL << "Increased Initial Window, CWND is set to " << state->snd_cwnd << "\n";
+ }
+ else
+ state->snd_cwnd = state->snd_effmss;
+
+ // TODO we should send the ACK from TcpConnection instead of TcpAlgorithmBase, this is standard TCP behavior
+ if (active) {
+ // finish connection setup with ACK (possibly piggybacked on data)
+ EV_INFO << "Completing connection setup by sending ACK (possibly piggybacked on data)\n";
+ if (sendDataWithFirstAck) {
+ if (!sendData(false))
+ conn->sendAck();
+ }
+ else {
+ conn->sendAck();
+ sendData(false);
+ }
+ }
+
+ if (state->keepalive_enabled) {
+ state->time_last_segment_received = simTime();
+ state->keepalive_probes_sent = 0;
+ conn->scheduleAfter(state->keepalive_idle_time, keepAliveTimer);
+ }
+}
+
+void TcpAlgorithmBase::connectionClosed()
+{
+ cancelEvent(rexmitTimer);
+ cancelEvent(persistTimer);
+ cancelEvent(delayedAckTimer);
+ cancelEvent(keepAliveTimer);
+ cancelEvent(tlpTimer);
+ cancelEvent(corkTimer);
+}
+
+void TcpAlgorithmBase::processTimer(cMessage *timer, TcpEventCode& event)
+{
+ if (timer == rexmitTimer)
+ processRexmitTimer(event);
+ else if (timer == persistTimer)
+ processPersistTimer(event);
+ else if (timer == delayedAckTimer)
+ processDelayedAckTimer(event);
+ else if (timer == keepAliveTimer)
+ processKeepAliveTimer(event);
+ else if (timer == tlpTimer)
+ processPtoTimer(event);
+ else if (timer == corkTimer)
+ processCorkTimer(event);
+ else
+ throw cRuntimeError(timer, "unrecognized timer");
+}
+
+void TcpAlgorithmBase::processCorkTimer(TcpEventCode& event)
+{
+ // Linux ICSK_TIME_PROBE0 fired for a corked partial: force it out with PSH
+ // (tcp_write_wakeup forces PSH). corkedDataPending is cleared by the flush.
+ conn->flushCorkedData(/*forcePush=*/true);
+}
+
+void TcpAlgorithmBase::scheduleCorkTimer()
+{
+ // Force-flush a withheld TCP_CORK/MSG_MORE partial at the RTO if nothing else
+ // (a later send, an incoming ACK, or an uncork) flushes it first.
+ if (corkTimer->isScheduled())
+ conn->cancelEvent(corkTimer);
+ conn->scheduleAfter(state->rexmit_timeout, corkTimer);
+}
+
+void TcpAlgorithmBase::cancelCorkTimer()
+{
+ if (corkTimer != nullptr && corkTimer->isScheduled())
+ conn->cancelEvent(corkTimer);
+}
+
+void TcpAlgorithmBase::schedulePto()
+{
+ // Linux tcp_schedule_loss_probe(): eligible while SACK-capable, not in loss
+ // recovery, with no SACKed data outstanding, and no probe already in flight.
+ if (!state->tlpEnabled || !state->sack_enabled || state->lossRecovery
+ || state->sackedBytes != 0 || state->tlpHighSeq != 0
+ || state->snd_una == state->snd_max)
+ return;
+
+ // PTO = 2*SRTT; with a single packet in flight add the peer's potential
+ // delayed-ACK wait. No RTT sample yet -> the current RTO.
+ simtime_t pto;
+ if (state->srtt > 0) {
+ pto = state->srtt * 2;
+ if (state->snd_max - state->snd_una <= state->snd_mss)
+ pto += minRexmitTimeout; // single packet in flight: allow for the peer's delayed ACK
+ else
+ pto += SimTime(2, SIMTIME_MS); // floor so a near-zero srtt cannot fire the
+ // probe between back-to-back ACKs of one flight
+ }
+ else
+ pto = state->rexmit_timeout;
+
+ // never fire later than the RTO would have
+ if (rexmitTimer->isScheduled()) {
+ simtime_t rtoRemaining = rexmitTimer->getArrivalTime() - simTime();
+ if (rtoRemaining < pto)
+ pto = rtoRemaining;
+ }
+ if (pto <= SIMTIME_ZERO)
+ return;
+
+ if (tlpTimer->isScheduled())
+ conn->cancelEvent(tlpTimer);
+ conn->scheduleAfter(pto, tlpTimer);
+ EV_DETAIL << "TLP: probe timeout armed for " << pto << "s\n";
+}
+
+void TcpAlgorithmBase::processPtoTimer(TcpEventCode& event)
+{
+ // RFC 8985 section 7.2 / Linux tcp_send_loss_probe(): the tail of the flight
+ // was not acked within the probe timeout. Send one probe segment so its ACK
+ // (or the SACK hole it exposes) triggers fast recovery instead of an RTO.
+ //
+ // Not while already in fast recovery: Linux shares the RETRANS/LOSS_PROBE icsk
+ // timer slot, so entering recovery arms the RTO and supersedes the PTO. INET's
+ // recovery can be entered by the RACK reordering timer WITHOUT restarting the RTO
+ // (a pure-SACK recovery advances no cumulative ACK), leaving a stale PTO armed; it
+ // must not fire a redundant last-segment probe once RACK/PRR own recovery.
+ if (!state->tlpEnabled || state->tlpHighSeq != 0 || state->lossRecovery)
+ return;
+ if (conn->sendTlpProbe()) {
+ state->tlpHighSeq = state->snd_max; // probe outstanding until this is acked
+ // Single timer slot, the other direction: a fired probe supersedes the
+ // pending RTO and re-arms it for a full period from now (Linux
+ // tcp_send_loss_probe's rearm_timer). schedulePto() caps the PTO at the
+ // RTO's remaining time, so both can be scheduled for the very same
+ // instant -- without this the RTO event still fires right after the
+ // probe and retransmits the head a full RTO period early.
+ if (rexmitTimer->isScheduled())
+ conn->cancelEvent(rexmitTimer);
+ conn->scheduleAfter(state->rexmit_timeout, rexmitTimer);
+ }
+}
+
+void TcpAlgorithmBase::processRexmitTimer(TcpEventCode& event)
+{
+ EV_DETAIL << "TCB: " << state->str() << "\n";
+
+ //"
+ // For any state if the retransmission timeout expires on a segment in
+ // the retransmission queue, send the segment at the front of the
+ // retransmission queue again, reinitialize the retransmission timer,
+ // and return.
+ //"
+ // Also: abort connection after max 12 retries.
+ //
+ // However, retransmission is actually more complicated than that
+ // in RFC 9293 above, we'll leave it to subclasses (e.g. TcpTahoe, TcpReno).
+ //
+ if (++state->rexmit_count > maxRexmitCount) {
+ EV_DETAIL << "Retransmission count exceeds " << maxRexmitCount << ", aborting connection\n";
+ conn->signalConnectionTimeout();
+ event = TCP_E_ABORT; // TODO maybe rather introduce a TCP_E_TIMEDOUT event
+ return;
+ }
+
+ EV_INFO << "Performing retransmission #" << state->rexmit_count
+ << "; increasing RTO from " << state->rexmit_timeout << "s ";
+
+ //
+ // Karn's algorithm is implemented below:
+ // (1) don't measure RTT for retransmitted packets.
+ // (2) RTO should be doubled after retransmission ("exponential back-off")
+ //
+
+ // restart the retransmission timer with twice the latest RTO value, or with the max, whichever is smaller
+ state->rexmit_timeout += state->rexmit_timeout;
+ if (state->rexmit_timeout > maxRexmitTimeout)
+ state->rexmit_timeout = maxRexmitTimeout;
+
+ conn->scheduleAfter(state->rexmit_timeout, rexmitTimer);
+
+ // Single timer slot (Linux shares the RETRANS and LOSS_PROBE slot): a fired
+ // RTO supersedes any pending loss probe. schedulePto() caps the PTO at the
+ // RTO's remaining time, so the two can be scheduled for the very same instant;
+ // without this cancel the RTO fires, retransmits, and then a stale TLP probe
+ // fires into the post-RTO state (a spurious extra segment, and -- before the
+ // sendTlpProbe bound fix -- a createSegmentWithBytes abort).
+ if (tlpTimer != nullptr && tlpTimer->isScheduled())
+ conn->cancelEvent(tlpTimer);
+
+ EV_INFO << " to " << state->rexmit_timeout << "s, and cancelling RTT measurement\n";
+
+ // cancel round-trip time measurement
+ state->rtseq_sendtime = 0;
+
+ state->numRtos++;
+
+ conn->emit(numRtosSignal, state->numRtos);
+
+ // if sacked_enabled reset sack related flags
+ if (state->sack_enabled) {
+ conn->getRexmitQueueForUpdate()->resetSackedBit();
+ conn->getRexmitQueueForUpdate()->resetRexmittedBit();
+
+ // RFC 6675, page 10: "If an RTO occurs during loss recovery as specified in this document,
+ // RecoveryPoint MUST be set to HighData. Further, the new value of
+ // RecoveryPoint MUST be preserved and the loss recovery algorithm
+ // outlined in this document MUST be terminated. In addition, a new
+ // recovery phase (as described in section 5) MUST NOT be initiated
+ // until HighACK is greater than or equal to the new value of
+ // RecoveryPoint."
+ if (state->lossRecovery) {
+ state->recoveryPoint = state->snd_max; // HighData = snd_max
+ EV_DETAIL << "Loss Recovery terminated.\n";
+ state->lossRecovery = false;
+ }
+ }
+
+ state->time_last_data_sent = simTime();
+
+ //
+ // Leave congestion window management and actual retransmission to
+ // subclasses (e.g. TcpTahoe, TcpReno).
+ //
+ // That is, subclasses will redefine this method, call us, then perform
+ // window adjustments and do the retransmission as they like.
+ //
+}
+
+void TcpAlgorithmBase::processPersistTimer(TcpEventCode& event)
+{
+ // Linux tcp_probe_timer / probe0 cadence (resolves the old FIXME): the
+ // zero-window probe interval starts at the current RTO (set at first arm,
+ // see receivedAckForUnackedData's zero-window branch) and DOUBLES per
+ // probe (icsk_backoff), capped at maxPersistTimeout -- it is not the
+ // fixed Stevens 5/5/6/12/24/48/60 table: with a ~100ms RTT the first
+ // probe goes out at ~300ms (slow-start-after-win-update pins this).
+ state->persist_timeout = state->persist_timeout * 2;
+ if (state->persist_timeout > maxPersistTimeout)
+ state->persist_timeout = maxPersistTimeout;
+
+ conn->scheduleAfter(state->persist_timeout, persistTimer);
+
+ // sending persist probe
+ conn->sendProbe();
+ state->zeroWindowProbesSent++;
+}
+
+void TcpAlgorithmBase::processDelayedAckTimer(TcpEventCode& event)
+{
+ if (state->adaptiveDelayedAcks) {
+ // a delayed ACK actually expired (Linux tcp_delack_timer_handler):
+ // in bulk mode the ATO was too optimistic -- inflate it (bounded by
+ // RTO); in interactive (pingpong) mode drop back out and deflate
+ if (state->pingpongCount < TCP_PINGPONG_THRESH) {
+ state->ackAto = state->ackAto * 2;
+ if (state->ackAto > state->rexmit_timeout)
+ state->ackAto = state->rexmit_timeout;
+ }
+ else {
+ state->pingpongCount = 0;
+ state->ackAto = TCP_ATO_MIN_S;
+ }
+ }
+ state->ack_now = true;
+ conn->sendAck();
+}
+
+void TcpAlgorithmBase::processKeepAliveTimer(TcpEventCode& event)
+{
+ // RFC 1122 4.2.3.6 keepalive mechanism, following the Linux tcp_keepalive_timer
+ // semantics (net/ipv4/tcp_timer.c).
+
+ // If there is unacknowledged data or data pending in the send queue, the
+ // retransmission timer already probes connection liveness; just re-arm.
+ if (state->snd_max != state->snd_una || !conn->isSendQueueEmpty()) {
+ state->keepalive_probes_sent = 0;
+ conn->scheduleAfter(state->keepalive_idle_time, keepAliveTimer);
+ return;
+ }
+
+ // If a segment was received recently, the connection is not idle yet.
+ simtime_t elapsed = simTime() - state->time_last_segment_received;
+ if (elapsed < state->keepalive_idle_time) {
+ state->keepalive_probes_sent = 0;
+ conn->scheduleAfter(state->keepalive_idle_time - elapsed, keepAliveTimer);
+ return;
+ }
+
+ // The connection is idle. If the peer failed to answer the allowed number of
+ // probes, abort the connection (Linux sends a RST; INET reuses the
+ // timeout-abort path, which notifies the app with TCP_I_TIMED_OUT).
+ if (state->keepalive_probes_sent >= state->keepalive_max_probes) {
+ EV_INFO << "Keepalive: peer did not respond to " << state->keepalive_max_probes
+ << " probes, aborting connection\n";
+ conn->signalConnectionTimeout();
+ event = TCP_E_ABORT;
+ return;
+ }
+
+ EV_INFO << "Keepalive: connection idle, sending probe #"
+ << (state->keepalive_probes_sent + 1) << "\n";
+ conn->sendKeepAliveProbe();
+ state->keepalive_probes_sent++;
+ conn->scheduleAfter(state->keepalive_interval, keepAliveTimer);
+}
+
+void TcpAlgorithmBase::startRexmitTimer()
+{
+ // start counting retransmissions for this seq number.
+ // Note: state->rexmit_timeout is set from rttMeasurementComplete().
+ state->rexmit_count = 0;
+
+ // single-slot discipline with the loss-probe timer (Linux shares one icsk
+ // timer slot between RETRANS and LOSS_PROBE): arming the RTO always disarms
+ // a pending probe.
+ if (tlpTimer != nullptr && tlpTimer->isScheduled())
+ conn->cancelEvent(tlpTimer);
+
+ // schedule timer
+ conn->scheduleAfter(state->rexmit_timeout, rexmitTimer);
+}
+
+void TcpAlgorithmBase::ensureRexmitTimerArmed()
+{
+ // TCP RTO invariant (Linux tcp_rearm_rto): unacknowledged data outstanding
+ // implies the retransmission timer must be running. receivedAckForUnackedData
+ // cancels the timer on an ACK that acks all previously-outstanding data, but
+ // RFC 6675 recovery (stepC) can then transmit fresh segments in the same ACK
+ // whose send path does not arm the timer, and the trailing sendData() may be
+ // cwnd-blocked (SWS) and send nothing. Re-arm here so that fresh data cannot
+ // be left outstanding with no timer -- otherwise, if it is lost, nothing ever
+ // retransmits it and the connection deadlocks.
+ if (state->snd_una != state->snd_max && !rexmitTimer->isScheduled())
+ startRexmitTimer();
+}
+
+void TcpAlgorithmBase::rttMeasurementComplete(simtime_t tSent, simtime_t tAcked)
+{
+ //
+ // Jacobson's algorithm for estimating RTT and adaptively setting RTO.
+ //
+ // Note: this implementation calculates in doubles. An impl. which uses
+ // 500ms ticks is available from old tcpmodule.cc:calcRetransTimer().
+ //
+
+ // RTT estimator per RFC 6298 (Jacobson/Karn), with Linux's variance-floor RTO.
+ // update smoothed RTT estimate (srtt) and variance (rttvar)
+ const double g = 0.125; // 1 / 8; (1 - alpha) where alpha == 7 / 8;
+ simtime_t newRTT = tAcked - tSent;
+
+ // track the minimum RTT (RACK loss detection); a running min (not windowed)
+ if (newRTT > 0 && (state->minRtt == 0 || newRTT < state->minRtt))
+ state->minRtt = newRTT;
+
+ simtime_t err = newRTT - state->srtt;
+
+ if (state->srtt == 0) {
+ state->srtt = newRTT;
+ state->rttvar = newRTT / 2;
+ }
+ else {
+ state->srtt += g * err;
+ state->rttvar += g * (fabs(err) - state->rttvar);
+ }
+
+ // Linux-style variance floor (tcp_set_rto): RTO = SRTT + max(4*RTTVAR, RTO_MIN),
+ // i.e. RTO >= SRTT + minRexmitTimeout, rather than clamping the final RTO from
+ // below.
+ simtime_t varTerm = 4 * state->rttvar;
+ if (varTerm < minRexmitTimeout)
+ varTerm = minRexmitTimeout;
+ simtime_t rto = state->srtt + varTerm;
+
+ if (rto > maxRexmitTimeout)
+ rto = maxRexmitTimeout;
+
+ state->rexmit_timeout = rto;
+
+ // record statistics
+ EV_DETAIL << "Measured RTT=" << (newRTT * 1000) << "ms, updated SRTT=" << (state->srtt * 1000)
+ << "ms, new RTO=" << (rto * 1000) << "ms\n";
+
+ conn->emit(rttSignal, newRTT);
+ conn->emit(srttSignal, state->srtt);
+ conn->emit(rttvarSignal, state->rttvar);
+ conn->emit(rtoSignal, rto);
+}
+
+void TcpAlgorithmBase::rttMeasurementCompleteUsingTS(uint32_t echoedTS)
+{
+ ASSERT(state->ts_enabled);
+
+ // Note: The TS option is using uint32_t values (ms precision) therefore we convert the current simTime also to a uint32_t value (ms precision)
+ // and then convert back to simtime_t to use rttMeasurementComplete() to update srtt and rttvar
+ uint32_t now = conn->convertSimtimeToTS(simTime());
+ simtime_t tSent = conn->convertTSToSimtime(echoedTS);
+ simtime_t tAcked = conn->convertTSToSimtime(now);
+ rttMeasurementComplete(tSent, tAcked);
+}
+
+bool TcpAlgorithmBase::sendData(bool sendCommandInvoked)
+{
+ // TCP Fast Open server (RFC 7413 section 4.2): response data may be sent
+ // from SYN_RCVD, before established() has initialized the congestion
+ // window -- initialize it here, the same initial window a regular
+ // connection would get (Linux initializes the TFO child socket's cwnd at
+ // creation). Only a fastopenAccelerated connection can reach
+ // sendData() with the pre-established cwnd of 0.
+ if (state->snd_cwnd == 0 && state->fastopenAccelerated) {
+ state->snd_cwnd = initialWindow();
+ EV_DETAIL << "Fast Open: initializing CWND to " << state->snd_cwnd << " for SYN_RCVD response data\n";
+ }
+
+ // RFC 5681, page 11: "When TCP has not received a segment for
+ // more than one retransmission timeout, cwnd is reduced to the value
+ // of the restart window (RW) before transmission begins.
+ // For the purposes of this standard, we define RW = IW.
+ // (...)
+ // Using the last time a segment was received to determine whether or
+ // not to decrease cwnd fails to deflate cwnd in the common case of
+ // persistent HTTP connections [HTH98].
+ // (...)
+ // Therefore, a TCP SHOULD set cwnd to no more than RW before beginning
+ // transmission if the TCP has not sent data in an interval exceeding
+ // the retransmission timeout."
+ if (!conn->isSendQueueEmpty()) { // do we have any data to send?
+ if ((simTime() - state->time_last_data_sent) > state->rexmit_timeout) {
+ // RFC 5681, page 11: "For the purposes of this standard, we define RW = min(IW,cwnd)."
+ state->snd_cwnd = std::min(initialWindow(), state->snd_cwnd);
+
+ EV_INFO << "Restarting idle connection, CWND is set to " << state->snd_cwnd << "\n";
+ }
+ }
+
+ //
+ // Send window is effectively the minimum of the congestion window (cwnd)
+ // and the advertised window (snd_wnd).
+ //
+ return conn->sendData(state->snd_cwnd);
+}
+
+void TcpAlgorithmBase::sendCommandInvoked()
+{
+ // try sending
+ sendData(true);
+}
+
+void TcpAlgorithmBase::incrQuickack(uint32_t maxQuickacks)
+{
+ // Budget of back-to-back immediate ACKs: enough to cover half the receive
+ // window in one-per-segment ACKs, at most maxQuickacks (Linux
+ // tcp_incr_quickack; rcv_mss approximated by our own MSS, since virtually
+ // all simulation setups are MSS-symmetric).
+ uint32_t mss = state->snd_mss > 0 ? state->snd_mss : 536;
+ uint32_t quickacks = state->rcv_wnd / (2 * mss);
+ if (quickacks == 0)
+ quickacks = 2;
+ if (quickacks > maxQuickacks)
+ quickacks = maxQuickacks;
+ if (quickacks > state->quickAckCounter)
+ state->quickAckCounter = quickacks;
+}
+
+void TcpAlgorithmBase::enterQuickackMode(uint32_t maxQuickacks)
+{
+ incrQuickack(maxQuickacks);
+ state->pingpongCount = 0; // leave interactive mode
+ state->ackAto = TCP_ATO_MIN_S;
+}
+
+bool TcpAlgorithmBase::inQuickackMode() const
+{
+ return state->quickAckCounter > 0 && state->pingpongCount < TCP_PINGPONG_THRESH;
+}
+
+void TcpAlgorithmBase::receivedOutOfOrderSegment()
+{
+ // out-of-order data starts (or refreshes) a quickack burst: the sender is
+ // likely in loss recovery and needs feedback per segment
+ if (state->adaptiveDelayedAcks)
+ enterQuickackMode(TCP_MAX_QUICKACKS);
+ state->ack_now = true;
+ EV_INFO << "Out-of-order segment, sending immediate ACK\n";
+ conn->sendAck();
+}
+
+void TcpAlgorithmBase::dataArrivedAtoUpdate()
+{
+ // Adapt the delayed-ACK engine to the observed inter-segment arrival gap
+ // (Linux tcp_event_data_recv): the first data segment initializes a full
+ // quickack budget; closely spaced arrivals shrink the ATO toward its
+ // 40ms floor; a gap above the retransmission timeout means the sender
+ // stalled waiting for ACKs -- resume quick ACKing.
+ simtime_t now = simTime();
+ if (state->ackAto == SIMTIME_ZERO) {
+ incrQuickack(TCP_MAX_QUICKACKS);
+ state->ackAto = TCP_ATO_MIN_S;
+ }
+ else {
+ simtime_t m = now - state->lastDataRecvTime;
+ if (m <= TCP_ATO_MIN_S / 2)
+ state->ackAto = state->ackAto / 2 + TCP_ATO_MIN_S / 2;
+ else if (m < state->ackAto) {
+ state->ackAto = state->ackAto / 2 + m;
+ if (state->ackAto > state->rexmit_timeout)
+ state->ackAto = state->rexmit_timeout;
+ }
+ else if (m > state->rexmit_timeout)
+ incrQuickack(TCP_MAX_QUICKACKS);
+ }
+ state->lastDataRecvTime = now;
+}
+
+void TcpAlgorithmBase::scheduleDelayedAck()
+{
+ // Linux tcp_send_delayed_ack: the armed timeout is the ATO bounded by the
+ // measured RTT (a delayed ACK should not stall the sender's clock for
+ // longer than a round trip) and by the 200ms ceiling.
+ simtime_t ato = state->ackAto;
+ if (ato > TCP_DELACK_MIN_S) {
+ simtime_t maxAto = TCP_DELACK_MAX_S;
+ if (state->srtt > SIMTIME_ZERO) {
+ simtime_t rtt = state->srtt < TCP_DELACK_MIN_S ? TCP_DELACK_MIN_S : state->srtt;
+ if (rtt < maxAto)
+ maxAto = rtt;
+ }
+ if (ato > maxAto)
+ ato = maxAto;
+ }
+ if (ato > TCP_DELACK_MAX_S)
+ ato = TCP_DELACK_MAX_S;
+
+ simtime_t timeout = simTime() + ato;
+ if (delayedAckTimer->isScheduled()) {
+ // an earlier deadline stands; and if it is about to fire anyway,
+ // just send the ACK now
+ if (delayedAckTimer->getArrivalTime() <= simTime() + ato / 4) {
+ cancelEvent(delayedAckTimer);
+ state->ack_now = true;
+ conn->sendAck();
+ return;
+ }
+ if (delayedAckTimer->getArrivalTime() < timeout)
+ return; // keep the earlier one
+ cancelEvent(delayedAckTimer);
+ }
+ conn->scheduleAt(timeout, delayedAckTimer);
+}
+
+void TcpAlgorithmBase::receiveSeqChanged()
+{
+ // If we send a data segment already (with the updated seqNo) there is no need to send an additional ACK
+ if (state->full_sized_segment_counter == 0 && !state->ack_now && state->last_ack_sent == state->rcv_nxt && !delayedAckTimer->isScheduled()) { // ackSent?
+// tcpEV << "ACK has already been sent (possibly piggybacked on data)\n";
+ }
+ else {
+ if (!state->delayed_acks_enabled) { // delayed ACK disabled
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK disabled) sending ACK now\n";
+ conn->sendAck();
+ }
+ else if (state->adaptiveDelayedAcks) {
+ // Linux-shaped decision (__tcp_ack_snd_check): immediate ACK when
+ // more than one full frame is pending, in quickack mode, or when
+ // protocol state demands one; otherwise arm the ADAPTIVE delayed
+ // ACK. The ATO bookkeeping runs first (tcp_event_data_recv).
+ dataArrivedAtoUpdate();
+ uint32_t mss = state->snd_mss > 0 ? state->snd_mss : 536;
+ bool moreThanOneFrame = (state->rcv_nxt - state->last_ack_sent) > mss;
+ // An arrival accepted BEYOND the advertised-window promise (the
+ // empty-queue over-accept) is not immediate-ACKed by the kernel --
+ // its selftest pins this ("It does not trigger an immediate ACK",
+ // rcv_neg_window) -- so suppress the quickack/multi-frame immediate
+ // arms for this decision; a protocol-mandated ack_now still wins.
+ bool overAccept = conn->overWindowAcceptPending;
+ conn->overWindowAcceptPending = false;
+ if (state->ack_now || ((moreThanOneFrame || inQuickackMode()) && !overAccept)) {
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", sending immediate ACK ("
+ << (state->ack_now ? "ack_now" : moreThanOneFrame ? "second full frame" : "quickack mode")
+ << ", quickack budget " << state->quickAckCounter << ")\n";
+ conn->sendAck();
+ }
+ else {
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", arming adaptive delayed ACK (ato="
+ << state->ackAto << ")\n";
+ scheduleDelayedAck();
+ }
+ }
+ else { // delayed ACK enabled
+ if (state->ack_now) {
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled, but ack_now is set) sending ACK now\n";
+ conn->sendAck();
+ }
+ // RFC 1122, page 96: "in a stream of full-sized segments there SHOULD be an ACK for at least every second segment."
+ else if (state->full_sized_segment_counter >= state->delayedAckFrameCount) {
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled, but full_sized_segment_counter=" << state->full_sized_segment_counter << ") sending ACK now\n";
+ conn->sendAck();
+ }
+ else {
+ EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled and full_sized_segment_counter=" << state->full_sized_segment_counter << ") scheduling ACK\n";
+ if (!delayedAckTimer->isScheduled()) // schedule delayed ACK timer if not already running
+ conn->scheduleAfter(delayedAckTimeout, delayedAckTimer);
+ }
+ }
+ }
+}
+
+void TcpAlgorithmBase::receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ // A pure window-update ACK that reopened a closed window ends the persist
+ // state and transmits queued data immediately (Linux FLAG_WIN_UPDATE ->
+ // tcp_data_snd_check; without this the data waited for the next
+ // zero-window probe's ACK, one whole doubled persist period late).
+ // Not gated on the persist timer being armed: when the ZERO window came
+ // with the handshake itself, nothing was ever in flight, no ACK ever
+ // acked data, and the persist timer was never started -- yet queued data
+ // must still go out the moment the window opens (tcp-info-rwnd-limited
+ // pins it). Restricted to nothing-in-flight so ordinary dupacks during
+ // loss recovery never reach the send path from here.
+ if (state->snd_wnd > 0 && state->snd_una == state->snd_max) {
+ if (persistTimer->isScheduled()) {
+ EV_INFO << "Window reopened by a pure window update: canceling PERSIST timer\n";
+ cancelEvent(persistTimer);
+ state->persist_factor = 0;
+ }
+ sendData(false);
+ }
+
+ countDuplicateAck(tcpHeader, payloadLength);
+
+ //
+ // Leave congestion window management and possible sending data to
+ // subclasses (e.g. TcpTahoe, TcpReno).
+ //
+ // That is, subclasses will redefine this method, call us, then perform
+ // window adjustments and send data (if there's room in the window).
+ //
+}
+
+bool TcpAlgorithmBase::isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ return state->snd_una == tcpHeader->getAckNo() && payloadLength == 0 && state->snd_una != state->snd_max;
+}
+
+void TcpAlgorithmBase::countDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ if (isDuplicateAck(tcpHeader, payloadLength)) {
+ // during loss recovery the recovery strategy owns the counter
+ if (!state->lossRecovery) {
+ state->dupacks++;
+ conn->emit(dupAcksSignal, state->dupacks);
+ }
+ receivedDuplicateAck();
+ }
+ else {
+ // if doesn't qualify as duplicate ACK, just ignore it.
+ if (payloadLength == 0) {
+ if (state->snd_una != tcpHeader->getAckNo())
+ EV_DETAIL << "Old ACK: ackNo < snd_una\n";
+ else if (state->snd_una == state->snd_max)
+ EV_DETAIL << "ACK looks duplicate but we have currently no unacked data (snd_una == snd_max)\n";
+ }
+ // reset counter
+ state->dupacks = 0;
+ conn->emit(dupAcksSignal, state->dupacks);
+ }
+}
+
+void TcpAlgorithmBase::receivedAckForUnackedData(uint32_t firstSeqAcked)
+{
+ if (!state->ts_enabled) {
+ // if round-trip time measurement is running, check if rtseq has been acked
+ if (state->rtseq_sendtime != 0 && seqLess(state->rtseq, state->snd_una)) {
+ // print value
+ EV_DETAIL << "Round-trip time measured on rtseq=" << state->rtseq << ": "
+ << floor((simTime() - state->rtseq_sendtime) * 1000 + 0.5) << "ms\n";
+
+ rttMeasurementComplete(state->rtseq_sendtime, simTime()); // update RTT variables with new value
+
+ // measurement finished
+ state->rtseq_sendtime = 0;
+ }
+ }
+
+ //
+ // handling of retransmission timer: if the ACK is for the last segment sent
+ // (no data in flight), cancel the timer, otherwise restart the timer
+ // with the current RTO value.
+ //
+ if (state->snd_una == state->snd_max) {
+ if (rexmitTimer->isScheduled()) {
+ EV_INFO << "ACK acks all outstanding segments, cancel REXMIT timer\n";
+ cancelEvent(rexmitTimer);
+ }
+ else
+ EV_INFO << "There were no outstanding segments, nothing new in this ACK.\n";
+ }
+ else {
+ EV_INFO << "ACK acks some but not all outstanding segments ("
+ << (state->snd_max - state->snd_una) << " bytes outstanding), "
+ << "restarting REXMIT timer\n";
+ cancelEvent(rexmitTimer);
+ startRexmitTimer();
+ }
+
+ //
+ // handling of PERSIST timer:
+ // If data sender received a zero-sized window, check retransmission timer.
+ // If retransmission timer is not scheduled, start PERSIST timer if not already
+ // running.
+ //
+ // If data sender received a non zero-sized window, check PERSIST timer.
+ // If PERSIST timer is scheduled, cancel PERSIST timer.
+ //
+ if (state->snd_wnd == 0) { // received zero-sized window?
+ if (rexmitTimer->isScheduled()) {
+ if (persistTimer->isScheduled()) {
+ EV_INFO << "Received zero-sized window and REXMIT timer is running therefore PERSIST timer is canceled.\n";
+ cancelEvent(persistTimer);
+ state->persist_factor = 0;
+ }
+ else
+ EV_INFO << "Received zero-sized window and REXMIT timer is running therefore PERSIST timer is not started.\n";
+ }
+ else {
+ if (!persistTimer->isScheduled()) {
+ EV_INFO << "Received zero-sized window therefore PERSIST timer is started.\n";
+ // Linux probe0: the first probe fires one RTO after the
+ // window closed; subsequent probes double from there
+ state->persist_timeout = state->rexmit_timeout;
+ conn->scheduleAfter(state->persist_timeout, persistTimer);
+ }
+ else
+ EV_INFO << "Received zero-sized window and PERSIST timer is already running.\n";
+ }
+ }
+ else { // received non zero-sized window?
+ if (persistTimer->isScheduled()) {
+ EV_INFO << "Received non zero-sized window therefore PERSIST timer is canceled.\n";
+ cancelEvent(persistTimer);
+ state->persist_factor = 0;
+ }
+ }
+
+ state->dupacks = 0;
+ conn->emit(dupAcksSignal, state->dupacks);
+
+ //
+ // Leave congestion window management and possible sending data to
+ // subclasses (e.g. TcpTahoe, TcpReno).
+ //
+ // That is, subclasses will redefine this method, call us, then perform
+ // window adjustments and send data (if there's room in the window).
+ //
+}
+
+void TcpAlgorithmBase::receivedDuplicateAck()
+{
+ EV_INFO << "Duplicate ACK #" << state->dupacks << "\n";
+
+ bool fullSegmentsOnly = state->nagle_enabled && state->snd_una != state->snd_max;
+ if (state->dupacks < state->dupthresh && state->limited_transmit_enabled) // DUPTRESH = 3
+ conn->sendOneNewSegment(fullSegmentsOnly, state->snd_cwnd); // RFC 3042
+
+ //
+ // Leave to subclasses (e.g. TcpTahoe, TcpReno) whatever they want to do
+ // on duplicate Acks.
+ //
+ // That is, subclasses will redefine this method, call us, then perform
+ // whatever action they want to do on dupAcks (e.g. retransmitting one segment).
+ //
+}
+
+void TcpAlgorithmBase::receivedAckForUnsentData(uint32_t seq)
+{
+ // Note: In this case no immediate ACK will be send because not mentioned
+ // in [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 861].
+ // To force immediate ACK use:
+// state->ack_now = true;
+// tcpEV << "ACK acks something not yet sent, sending immediate ACK\n";
+ EV_INFO << "ACK acks something not yet sent, sending ACK\n";
+ conn->sendAck();
+ state->dupacks = 0;
+ conn->emit(dupAcksSignal, state->dupacks);
+}
+
+void TcpAlgorithmBase::ackSent()
+{
+ // every ACK actually sent consumes one unit of the quickack budget
+ // (Linux tcp_event_ack_sent -> tcp_dec_quickack_mode)
+ if (state->adaptiveDelayedAcks && state->quickAckCounter > 0)
+ state->quickAckCounter--;
+ state->full_sized_segment_counter = 0; // reset counter
+ state->ack_now = false; // reset flag
+ state->last_ack_sent = state->rcv_nxt; // update last_ack_sent, needed for TS option
+ // if delayed ACK timer is running, cancel it
+ if (delayedAckTimer->isScheduled())
+ cancelEvent(delayedAckTimer);
+}
+
+void TcpAlgorithmBase::dataSent(uint32_t fromseq)
+{
+ // a data reply within one ATO of the last received packet is interactive
+ // ("pingpong") evidence -- it makes the receiver favor delayed ACKs
+ // (Linux tcp_event_data_sent, called for every data-bearing transmit;
+ // lrcvtime is seeded at connection establishment, see established())
+ if (state->adaptiveDelayedAcks && state->lastDataRecvTime > SIMTIME_ZERO
+ && simTime() - state->lastDataRecvTime < state->ackAto
+ && state->pingpongCount < TCP_PINGPONG_THRESH)
+ {
+ state->pingpongCount++;
+ }
+
+ // if retransmission timer not running, schedule it
+ if (!rexmitTimer->isScheduled()) {
+ EV_INFO << "Starting REXMIT timer\n";
+ startRexmitTimer();
+ }
+
+ // RFC 8985 7.2: (re)arm the loss probe for the new tail of the flight
+ schedulePto();
+
+ if (!state->ts_enabled) {
+ // start round-trip time measurement (if not already running)
+ if (state->rtseq_sendtime == 0) {
+ // remember this sequence number and when it was sent
+ state->rtseq = fromseq;
+ state->rtseq_sendtime = simTime();
+ EV_DETAIL << "Starting rtt measurement on seq=" << state->rtseq << "\n";
+ }
+ }
+
+ state->time_last_data_sent = simTime();
+
+ // record per-segment transmit times (shared facility used by Vegas/Westwood
+ // RTT sampling, and by RACK/Eifel loss recovery)
+ state->sentInfo.clearTo(state->snd_una);
+ // Loss probes and post-RTO retransmissions can move snd_nxt backwards, so a
+ // send may start below the range this list currently covers (it only records
+ // forward progress). Recording such a range would violate the list's
+ // contiguity invariant; the segment's timing is already tracked per-region in
+ // the rexmit queue, which is what RACK reads, so skip it here.
+ if (seqLess(fromseq, state->snd_max) && state->sentInfo.isInRange(fromseq))
+ state->sentInfo.set(fromseq, state->snd_max, simTime());
+}
+
+void TcpAlgorithmBase::segmentRetransmitted(uint32_t fromseq, uint32_t toseq)
+{
+ if (seqLess(fromseq, toseq) && state->sentInfo.isInRange(fromseq))
+ state->sentInfo.set(fromseq, toseq, simTime());
+}
+
+void TcpAlgorithmBase::restartRexmitTimer()
+{
+ if (rexmitTimer->isScheduled())
+ cancelEvent(rexmitTimer);
+
+ startRexmitTimer();
+}
+
+bool TcpAlgorithmBase::shouldMarkAck()
+{
+
+ // RFC 3168, pages 19-20:
+ // "When TCP receives a CE data packet at the destination end-system, the
+ // TCP data receiver sets the ECN-Echo flag in the TCP header of the
+ // subsequent ACK packet.
+ // ...
+ // After a TCP receiver sends an ACK packet with the ECN-Echo bit set,
+ // that TCP receiver continues to set the ECN-Echo flag in all the ACK
+ // packets it sends (whether they acknowledge CE data packets or non-CE
+ // data packets) until it receives a CWR packet (a packet with the CWR
+ // flag set). After the receipt of the CWR packet, acknowledgments for
+ // subsequent non-CE data packets do not have the ECN-Echo flag set."
+
+ if (state && state->ect) {
+ if (state->gotCeIndication) {
+ EV_INFO << "Received CE... ";
+ if (state->ecnEchoState)
+ EV_INFO << "Already in ecnEcho state\n";
+ else {
+ state->ecnEchoState = true;
+ EV << "Entering ecnEcho state\n";
+ }
+ state->gotCeIndication = false;
+ }
+ return state->ecnEchoState;
+ }
+ return false;
+}
+
+void TcpAlgorithmBase::processEcnInEstablished()
+{
+}
+
+uint32_t TcpAlgorithmBase::getBytesInFlight() const
+{
+ return state->snd_nxt - conn->getDataSndUna();
+}
+
+uint32_t TcpAlgorithmBase::calculateSsthreshForFastRecovery()
+{
+ // Default (RFC 5681 / RFC 6675 4.2): ssthresh = max(FlightSize/2, 2*SMSS),
+ // used by the Reno family; CUBIC overrides with cwnd*beta.
+ return std::max(getBytesInFlight() / 2, 2 * state->snd_mss);
+}
+
+uint32_t TcpAlgorithmBase::calculateSsthresh(uint32_t bytesInFlight)
+{
+ return std::max(bytesInFlight / 2, 2 * state->snd_effmss);
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/TcpBaseAlg.h b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h
similarity index 52%
rename from src/inet/transportlayer/tcp/flavours/TcpBaseAlg.h
rename to src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h
index c68ae8a520f..c163413e4e8 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpBaseAlg.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h
@@ -5,11 +5,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
//
-#ifndef __INET_TCPBASEALG_H
-#define __INET_TCPBASEALG_H
+#ifndef __INET_TCPALGORITHMBASE_H
+#define __INET_TCPALGORITHMBASE_H
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBaseState_m.h"
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlgState_m.h"
namespace inet {
namespace tcp {
@@ -22,7 +22,7 @@ namespace tcp {
* Implements:
* - delayed ACK algorithm (RFC 1122)
* - Jacobson's and Karn's algorithms for adaptive retransmission
- * - Nagle's algorithm (RFC 896) to prevent silly window syndrome
+ * - Nagle's algorithm (RFC 1122) to prevent silly window syndrome
* - Increased Initial Window (RFC 3390)
* - PERSIST timer
*
@@ -38,23 +38,17 @@ namespace tcp {
* and not touched after that. Subclasses may redefine any of the virtual
* functions here to add their congestion control code.
*/
-class INET_API TcpBaseAlg : public TcpAlgorithm
+class INET_API TcpAlgorithmBase : public TcpAlgorithm
{
protected:
- TcpBaseAlgStateVariables *& state; // alias to TcpAlgorithm's 'state'
+ TcpAlgorithmBaseStateVariables *& state; // alias to TcpAlgorithm's 'state'
cMessage *rexmitTimer;
cMessage *persistTimer;
cMessage *delayedAckTimer;
cMessage *keepAliveTimer;
-
- static simsignal_t cwndSignal; // will record changes to snd_cwnd
- static simsignal_t ssthreshSignal; // will record changes to ssthresh
- static simsignal_t rttSignal; // will record measured RTT
- static simsignal_t srttSignal; // will record smoothed RTT
- static simsignal_t rttvarSignal; // will record RTT variance (rttvar)
- static simsignal_t rtoSignal; // will record retransmission timeout
- static simsignal_t numRtosSignal; // will record total number of RTOs
+ cMessage *tlpTimer; // Tail Loss Probe PTO (RFC 8985 7.2); shares the RTO's single-slot discipline
+ cMessage *corkTimer; // TCP_CORK/MSG_MORE flush timer (Linux ICSK_TIME_PROBE0); fires at the RTO
protected:
/** @name Process REXMIT, PERSIST, DELAYED-ACK and KEEP-ALIVE timers */
@@ -63,6 +57,12 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
virtual void processPersistTimer(TcpEventCode& event);
virtual void processDelayedAckTimer(TcpEventCode& event);
virtual void processKeepAliveTimer(TcpEventCode& event);
+ /** Tail Loss Probe timeout: send a probe and remember snd_max in tlpHighSeq. */
+ virtual void processPtoTimer(TcpEventCode& event);
+ /** Cork flush timeout: force out the withheld TCP_CORK/MSG_MORE partial with PSH. */
+ virtual void processCorkTimer(TcpEventCode& event);
+ /** Linux tcp_schedule_loss_probe(): arm the PTO if the connection is TLP-eligible. */
+ virtual void schedulePto();
//@}
/**
@@ -70,12 +70,21 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
*/
virtual void startRexmitTimer();
+ /**
+ * Re-establish the TCP RTO invariant (Linux tcp_rearm_rto): if any
+ * unacknowledged data is outstanding but no retransmission timer is
+ * running, arm it. Call after ACK processing has finished sending, to
+ * cover data transmitted by RFC 6675 recovery (stepC) or SWS-blocked
+ * sends that would otherwise leave outstanding data with no timer.
+ */
+ void ensureRexmitTimerArmed();
+
/**
* Update state vars with new measured RTT value. Passing two simtime_t's
* will allow rttMeasurementComplete() to do calculations in double or
* in 200ms/500ms ticks, as needed)
*/
- virtual void rttMeasurementComplete(simtime_t tSent, simtime_t tAcked);
+ virtual void rttMeasurementComplete(simtime_t tSent, simtime_t tAcked) override;
/**
* Converting uint32_t echoedTS to simtime_t and calling rttMeasurementComplete()
@@ -88,6 +97,16 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
*/
virtual bool sendData(bool sendCommandInvoked);
+ virtual void receivedDuplicateAck();
+
+ /**
+ * Returns the configured initial congestion window in bytes according to
+ * state->init_cwnd_mode (RFC 2001 / RFC 3390 / RFC 6928 IW10). Used both for
+ * the initial cwnd and for the restart window after an idle period.
+ */
+ virtual uint32_t initialWindow() const;
+
+
/** Utility function */
cMessage *cancelEvent(cMessage *msg) { return conn->cancelEvent(msg); }
@@ -95,12 +114,12 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
/**
* Ctor.
*/
- TcpBaseAlg();
+ TcpAlgorithmBase();
/**
* Virtual dtor.
*/
- virtual ~TcpBaseAlg();
+ virtual ~TcpAlgorithmBase();
/**
* Create timers, etc.
@@ -118,15 +137,32 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
virtual void sendCommandInvoked() override;
+ virtual void scheduleCorkTimer() override;
+ virtual void cancelCorkTimer() override;
+
virtual void receivedOutOfOrderSegment() override;
+ // Linux-shaped adaptive receiver ACK dynamics (adaptiveDelayedAcks param):
+ // quickack budget, adaptive delayed-ACK timeout (ATO), pingpong mode
+ virtual void incrQuickack(uint32_t maxQuickacks);
+ virtual void enterQuickackMode(uint32_t maxQuickacks);
+ virtual bool inQuickackMode() const;
+ virtual void dataArrivedAtoUpdate();
+ virtual void scheduleDelayedAck();
+
virtual void receiveSeqChanged() override;
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ /** RFC 5681 duplicate-ACK test; flavours owning a recovery object defer to it. */
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength);
+
+ /** Maintains state->dupacks and dispatches receivedDuplicateAck(). */
+ virtual void countDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength);
- virtual void receivedDuplicateAck() override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
- virtual void receivedAckForDataNotYetSent(uint32_t seq) override;
+ virtual void receivedAckForUnsentData(uint32_t seq) override;
virtual void ackSent() override;
@@ -139,6 +175,14 @@ class INET_API TcpBaseAlg : public TcpAlgorithm
virtual bool shouldMarkAck() override;
virtual void processEcnInEstablished() override;
+
+ virtual uint32_t getBytesInFlight() const override;
+
+ virtual simtime_t getSrtt() const override { return state->srtt; }
+
+ virtual uint32_t calculateSsthreshForFastRecovery() override;
+
+ virtual uint32_t calculateSsthresh(uint32_t bytesInFlight) override;
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpBaseAlgState.msg b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBaseState.msg
similarity index 53%
rename from src/inet/transportlayer/tcp/flavours/TcpBaseAlgState.msg
rename to src/inet/transportlayer/tcp/flavours/TcpAlgorithmBaseState.msg
index 62d70a6001e..bf2ec79679c 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpBaseAlgState.msg
+++ b/src/inet/transportlayer/tcp/flavours/TcpAlgorithmBaseState.msg
@@ -5,6 +5,7 @@
//
import inet.transportlayer.tcp.TcpConnectionState;
+import inet.transportlayer.tcp.flavours.TcpSegmentTransmitInfoList;
cplusplus {{
#include "inet/transportlayer/tcp/TcpAlgorithm.h"
@@ -13,9 +14,9 @@ cplusplus {{
namespace inet::tcp;
///
-/// State variables for TcpBaseAlg.
+/// State variables for TcpAlgorithmBase.
///
-struct TcpBaseAlgStateVariables extends TcpStateVariables
+struct TcpAlgorithmBaseStateVariables extends TcpStateVariables
{
@descriptor(readonly);
@@ -29,6 +30,16 @@ struct TcpBaseAlgStateVariables extends TcpStateVariables
//@{
unsigned int persist_factor = 0; ///< factor needed for simplified PERSIST timer calculation
simtime_t persist_timeout = 5.0; ///< current persist timeout
+ uint32_t zeroWindowProbesSent = 0; ///< cumulative zero-window (persist) probes sent; surfaced as TcpStatusInfo::probes
+ //@}
+
+ /// keepalive (RFC 1122 4.2.3.6)
+ //@{
+ bool keepalive_enabled = false; ///< whether keepalive probing is active on this connection
+ simtime_t keepalive_idle_time = 7200; ///< idle time before the first probe
+ simtime_t keepalive_interval = 75; ///< interval between probes
+ int keepalive_max_probes = 9; ///< number of unanswered probes before abort
+ int keepalive_probes_sent = 0; ///< probes sent in the current idle episode
//@}
/// congestion window
@@ -45,7 +56,8 @@ struct TcpBaseAlgStateVariables extends TcpStateVariables
/// round-trip time estimation (Jacobson's algorithm)
//@{
simtime_t srtt = 0; ///< smoothed round-trip time
- simtime_t rttvar = 3.0 / 4.0; ///< variance of round-trip time
+ simtime_t rttvar = 0; ///< variance of round-trip time (RFC 6298: seeded to R/2 on the first sample)
+ bool rtt_measured = false; ///< whether a first RTT sample has been taken (RFC 6298 initialization)
//@}
/// number of RTOs
@@ -53,14 +65,19 @@ struct TcpBaseAlgStateVariables extends TcpStateVariables
uint32_t numRtos = 0; ///< total number of RTOs
//@}
- /// RFC 3782 variables
+ /// RFC 6582 variables
+ //@{
+ uint32_t recover = iss; ///< recover (RFC 6582)
+ bool firstPartialACK = false; ///< first partial acknowledgement (RFC 6582)
+ //@}
+
+ /// per-segment transmit times, shared by all flavours (RTT sampling, RACK/Eifel input)
//@{
- uint32_t recover = iss; ///< recover (RFC 3782)
- bool firstPartialACK = false; ///< first partial acknowledgement (RFC 3782)
+ TcpSegmentTransmitInfoList sentInfo;
//@}
};
-cplusplus(TcpBaseAlgStateVariables) {{
+cplusplus(TcpAlgorithmBaseStateVariables) {{
public:
virtual std::string str() const override;
virtual std::string detailedInfo() const override;
diff --git a/src/inet/transportlayer/tcp/flavours/TcpBaseAlg.cc b/src/inet/transportlayer/tcp/flavours/TcpBaseAlg.cc
deleted file mode 100644
index 2dab1ed6112..00000000000
--- a/src/inet/transportlayer/tcp/flavours/TcpBaseAlg.cc
+++ /dev/null
@@ -1,645 +0,0 @@
-//
-// Copyright (C) 2004 OpenSim Ltd.
-// Copyright (C) 2009-2010 Thomas Reschka
-//
-// SPDX-License-Identifier: LGPL-3.0-or-later
-//
-
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
-
-#include "inet/transportlayer/tcp/Tcp.h"
-#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
-
-namespace inet {
-namespace tcp {
-
-//
-// Some constants below. MIN_REXMIT_TIMEOUT is the minimum allowed retransmit
-// interval. It is currently one second but e.g. a FreeBSD kernel comment says
-// it "will ultimately be reduced to 3 ticks for algorithmic stability,
-// leaving the 200ms variance to deal with delayed-acks, protocol overheads.
-// A 1 second minimum badly breaks throughput on any network faster then
-// a modem that has minor but continuous packet loss unrelated to congestion,
-// such as on a wireless network."
-//
-// RFC 1122, page 95:
-// "A TCP SHOULD implement a delayed ACK, but an ACK should not
-// be excessively delayed; in particular, the delay MUST be
-// less than 0.5 seconds, and in a stream of full-sized
-// segments there SHOULD be an ACK for at least every second
-// segment."
-
-#define DELAYED_ACK_TIMEOUT 0.2 // 200ms (RFC 1122: MUST be less than 0.5 seconds)
-#define MAX_REXMIT_COUNT 12 // 12 retries
-#define MIN_REXMIT_TIMEOUT 1.0 // 1s
-//#define MIN_REXMIT_TIMEOUT 0.6 // 600ms (3 ticks)
-#define MAX_REXMIT_TIMEOUT 240 // 2 * MSL (RFC 1122)
-#define MIN_PERSIST_TIMEOUT 5 // 5s
-#define MAX_PERSIST_TIMEOUT 60 // 60s
-
-std::string TcpBaseAlgStateVariables::str() const
-{
- std::stringstream out;
- out << TcpStateVariables::str();
- out << " snd_cwnd=" << snd_cwnd;
- out << " rto=" << rexmit_timeout;
- return out.str();
-}
-
-std::string TcpBaseAlgStateVariables::detailedInfo() const
-{
- std::stringstream out;
- out << TcpStateVariables::detailedInfo();
- out << "snd_cwnd=" << snd_cwnd << "\n";
- out << "rto=" << rexmit_timeout << "\n";
- out << "persist_timeout=" << persist_timeout << "\n";
- // TODO add others too
- return out.str();
-}
-
-simsignal_t TcpBaseAlg::cwndSignal = cComponent::registerSignal("cwnd"); // will record changes to snd_cwnd
-simsignal_t TcpBaseAlg::ssthreshSignal = cComponent::registerSignal("ssthresh"); // will record changes to ssthresh
-simsignal_t TcpBaseAlg::rttSignal = cComponent::registerSignal("rtt"); // will record measured RTT
-simsignal_t TcpBaseAlg::srttSignal = cComponent::registerSignal("srtt"); // will record smoothed RTT
-simsignal_t TcpBaseAlg::rttvarSignal = cComponent::registerSignal("rttvar"); // will record RTT variance (rttvar)
-simsignal_t TcpBaseAlg::rtoSignal = cComponent::registerSignal("rto"); // will record retransmission timeout
-simsignal_t TcpBaseAlg::numRtosSignal = cComponent::registerSignal("numRtos"); // will record total number of RTOs
-
-TcpBaseAlg::TcpBaseAlg() : TcpAlgorithm(),
- state((TcpBaseAlgStateVariables *&)TcpAlgorithm::state)
-{
- rexmitTimer = persistTimer = delayedAckTimer = keepAliveTimer = nullptr;
-}
-
-TcpBaseAlg::~TcpBaseAlg()
-{
- // Note: don't delete "state" here, it'll be deleted from TcpConnection
-
- // cancel and delete timers
- if (rexmitTimer)
- delete cancelEvent(rexmitTimer);
- if (persistTimer)
- delete cancelEvent(persistTimer);
- if (delayedAckTimer)
- delete cancelEvent(delayedAckTimer);
- if (keepAliveTimer)
- delete cancelEvent(keepAliveTimer);
-}
-
-void TcpBaseAlg::initialize()
-{
- TcpAlgorithm::initialize();
-
- rexmitTimer = new cMessage("REXMIT");
- persistTimer = new cMessage("PERSIST");
- delayedAckTimer = new cMessage("DELAYEDACK");
- keepAliveTimer = new cMessage("KEEPALIVE");
-
- rexmitTimer->setContextPointer(conn);
- persistTimer->setContextPointer(conn);
- delayedAckTimer->setContextPointer(conn);
- keepAliveTimer->setContextPointer(conn);
-}
-
-void TcpBaseAlg::established(bool active)
-{
- // initialize cwnd (we may learn SMSS during connection setup)
-
- // RFC 3390, page 2: "The upper bound for the initial window is given more precisely in
- // (1):
- //
- // min (4*MSS, max (2*MSS, 4380 bytes)) (1)
- //
- // Note: Sending a 1500 byte packet indicates a maximum segment size
- // (MSS) of 1460 bytes (assuming no IP or TCP options). Therefore,
- // limiting the initial window's MSS to 4380 bytes allows the sender to
- // transmit three segments initially in the common case when using 1500
- // byte packets.
- //
- // Equivalently, the upper bound for the initial window size is based on
- // the MSS, as follows:
- //
- // If (MSS <= 1095 bytes)
- // then win <= 4 * MSS;
- // If (1095 bytes < MSS < 2190 bytes)
- // then win <= 4380;
- // If (2190 bytes <= MSS)
- // then win <= 2 * MSS;
- //
- // This increased initial window is optional: a TCP MAY start with a
- // larger initial window. However, we expect that most general-purpose
- // TCP implementations would choose to use the larger initial congestion
- // window given in equation (1) above.
- //
- // This upper bound for the initial window size represents a change from
- // RFC 2581 [RFC2581], which specified that the congestion window be
- // initialized to one or two segments.
- // (...)
- // If the SYN or SYN/ACK is
- // lost, the initial window used by a sender after a correctly
- // transmitted SYN MUST be one segment consisting of MSS bytes."
- if (state->increased_IW_enabled && state->syn_rexmit_count == 0) {
- state->snd_cwnd = std::min(4 * state->snd_mss, std::max(2 * state->snd_mss, (uint32_t)4380));
- EV_DETAIL << "Enabled Increased Initial Window, CWND is set to " << state->snd_cwnd << "\n";
- }
- // RFC 2001, page 3:
- // " 1. Initialization for a given connection sets cwnd to one segment
- // and ssthresh to 65535 bytes."
- else
- state->snd_cwnd = state->snd_mss; // RFC 2001
-
- if (active) {
- // finish connection setup with ACK (possibly piggybacked on data)
- EV_INFO << "Completing connection setup by sending ACK (possibly piggybacked on data)\n";
- if (!sendData(false)) // FIXME - This condition is never true because the buffer is empty (at this time) therefore the first ACK is never piggyback on data
- conn->sendAck();
- }
-}
-
-void TcpBaseAlg::connectionClosed()
-{
- cancelEvent(rexmitTimer);
- cancelEvent(persistTimer);
- cancelEvent(delayedAckTimer);
- cancelEvent(keepAliveTimer);
-}
-
-void TcpBaseAlg::processTimer(cMessage *timer, TcpEventCode& event)
-{
- if (timer == rexmitTimer)
- processRexmitTimer(event);
- else if (timer == persistTimer)
- processPersistTimer(event);
- else if (timer == delayedAckTimer)
- processDelayedAckTimer(event);
- else if (timer == keepAliveTimer)
- processKeepAliveTimer(event);
- else
- throw cRuntimeError(timer, "unrecognized timer");
-}
-
-void TcpBaseAlg::processRexmitTimer(TcpEventCode& event)
-{
- EV_DETAIL << "TCB: " << state->str() << "\n";
-
- //"
- // For any state if the retransmission timeout expires on a segment in
- // the retransmission queue, send the segment at the front of the
- // retransmission queue again, reinitialize the retransmission timer,
- // and return.
- //"
- // Also: abort connection after max 12 retries.
- //
- // However, retransmission is actually more complicated than that
- // in RFC 793 above, we'll leave it to subclasses (e.g. TcpTahoe, TcpReno).
- //
- if (++state->rexmit_count > MAX_REXMIT_COUNT) {
- EV_DETAIL << "Retransmission count exceeds " << MAX_REXMIT_COUNT << ", aborting connection\n";
- conn->signalConnectionTimeout();
- event = TCP_E_ABORT; // TODO maybe rather introduce a TCP_E_TIMEDOUT event
- return;
- }
-
- EV_INFO << "Performing retransmission #" << state->rexmit_count
- << "; increasing RTO from " << state->rexmit_timeout << "s ";
-
- //
- // Karn's algorithm is implemented below:
- // (1) don't measure RTT for retransmitted packets.
- // (2) RTO should be doubled after retransmission ("exponential back-off")
- //
-
- // restart the retransmission timer with twice the latest RTO value, or with the max, whichever is smaller
- state->rexmit_timeout += state->rexmit_timeout;
- if (state->rexmit_timeout > MAX_REXMIT_TIMEOUT)
- state->rexmit_timeout = MAX_REXMIT_TIMEOUT;
-
- conn->scheduleAfter(state->rexmit_timeout, rexmitTimer);
-
- EV_INFO << " to " << state->rexmit_timeout << "s, and cancelling RTT measurement\n";
-
- // cancel round-trip time measurement
- state->rtseq_sendtime = 0;
-
- state->numRtos++;
-
- conn->emit(numRtosSignal, state->numRtos);
-
- // if sacked_enabled reset sack related flags
- if (state->sack_enabled) {
- conn->getRexmitQueueForUpdate()->resetSackedBit();
- conn->getRexmitQueueForUpdate()->resetRexmittedBit();
-
- // RFC 3517, page 8: "If an RTO occurs during loss recovery as specified in this document,
- // RecoveryPoint MUST be set to HighData. Further, the new value of
- // RecoveryPoint MUST be preserved and the loss recovery algorithm
- // outlined in this document MUST be terminated. In addition, a new
- // recovery phase (as described in section 5) MUST NOT be initiated
- // until HighACK is greater than or equal to the new value of
- // RecoveryPoint."
- if (state->lossRecovery) {
- state->recoveryPoint = state->snd_max; // HighData = snd_max
- EV_DETAIL << "Loss Recovery terminated.\n";
- state->lossRecovery = false;
- }
- }
-
- state->time_last_data_sent = simTime();
-
- //
- // Leave congestion window management and actual retransmission to
- // subclasses (e.g. TcpTahoe, TcpReno).
- //
- // That is, subclasses will redefine this method, call us, then perform
- // window adjustments and do the retransmission as they like.
- //
-}
-
-void TcpBaseAlg::processPersistTimer(TcpEventCode& event)
-{
- // setup and restart the PERSIST timer
- // FIXME Calculation of PERSIST timer is not as simple as done here!
- // It depends on RTT calculations and is bounded to 5-60 seconds.
- // This simplified PERSIST timer calculation generates values
- // as presented in [Stevens, W.R.: TCP/IP Illustrated, Volume 1, chapter 22.2]
- // (5, 5, 6, 12, 24, 48, 60, 60, 60...)
- if (state->persist_factor == 0)
- state->persist_factor++;
- else if (state->persist_factor < 64)
- state->persist_factor = state->persist_factor * 2;
-
- state->persist_timeout = state->persist_factor * 1.5; // 1.5 is a factor for typical LAN connection [Stevens, W.R.: TCP/IP Ill. Vol. 1, chapter 22.2]
-
- // PERSIST timer is bounded to 5-60 seconds
- if (state->persist_timeout < MIN_PERSIST_TIMEOUT)
- state->rexmit_timeout = MIN_PERSIST_TIMEOUT;
-
- if (state->persist_timeout > MAX_PERSIST_TIMEOUT)
- state->rexmit_timeout = MAX_PERSIST_TIMEOUT;
-
- conn->scheduleAfter(state->persist_timeout, persistTimer);
-
- // sending persist probe
- conn->sendProbe();
-}
-
-void TcpBaseAlg::processDelayedAckTimer(TcpEventCode& event)
-{
- state->ack_now = true;
- conn->sendAck();
-}
-
-void TcpBaseAlg::processKeepAliveTimer(TcpEventCode& event)
-{
- // TODO
- // RFC 1122, page 102:
- // "A "keep-alive" mechanism periodically probes the other
- // end of a connection when the connection is otherwise
- // idle, even when there is no data to be sent. The TCP
- // specification does not include a keep-alive mechanism
- // because it could: (1) cause perfectly good connections
- // to break during transient Internet failures; (2)
- // consume unnecessary bandwidth ("if no one is using the
- // connection, who cares if it is still good?"); and (3)
- // cost money for an Internet path that charges for
- // packets."
-}
-
-void TcpBaseAlg::startRexmitTimer()
-{
- // start counting retransmissions for this seq number.
- // Note: state->rexmit_timeout is set from rttMeasurementComplete().
- state->rexmit_count = 0;
-
- // schedule timer
- conn->scheduleAfter(state->rexmit_timeout, rexmitTimer);
-}
-
-void TcpBaseAlg::rttMeasurementComplete(simtime_t tSent, simtime_t tAcked)
-{
- //
- // Jacobson's algorithm for estimating RTT and adaptively setting RTO.
- //
- // Note: this implementation calculates in doubles. An impl. which uses
- // 500ms ticks is available from old tcpmodule.cc:calcRetransTimer().
- //
-
- // update smoothed RTT estimate (srtt) and variance (rttvar)
- const double g = 0.125; // 1 / 8; (1 - alpha) where alpha == 7 / 8;
- simtime_t newRTT = tAcked - tSent;
-
- simtime_t& srtt = state->srtt;
- simtime_t& rttvar = state->rttvar;
-
- simtime_t err = newRTT - srtt;
-
- srtt += g * err;
- rttvar += g * (fabs(err) - rttvar);
-
- // assign RTO (here: rexmit_timeout) a new value
- simtime_t rto = srtt + 4 * rttvar;
-
- if (rto > MAX_REXMIT_TIMEOUT)
- rto = MAX_REXMIT_TIMEOUT;
- else if (rto < MIN_REXMIT_TIMEOUT)
- rto = MIN_REXMIT_TIMEOUT;
-
- state->rexmit_timeout = rto;
-
- // record statistics
- EV_DETAIL << "Measured RTT=" << (newRTT * 1000) << "ms, updated SRTT=" << (srtt * 1000)
- << "ms, new RTO=" << (rto * 1000) << "ms\n";
-
- conn->emit(rttSignal, newRTT);
- conn->emit(srttSignal, srtt);
- conn->emit(rttvarSignal, rttvar);
- conn->emit(rtoSignal, rto);
-}
-
-void TcpBaseAlg::rttMeasurementCompleteUsingTS(uint32_t echoedTS)
-{
- ASSERT(state->ts_enabled);
-
- // Note: The TS option is using uint32_t values (ms precision) therefore we convert the current simTime also to a uint32_t value (ms precision)
- // and then convert back to simtime_t to use rttMeasurementComplete() to update srtt and rttvar
- uint32_t now = conn->convertSimtimeToTS(simTime());
- simtime_t tSent = conn->convertTSToSimtime(echoedTS);
- simtime_t tAcked = conn->convertTSToSimtime(now);
- rttMeasurementComplete(tSent, tAcked);
-}
-
-bool TcpBaseAlg::sendData(bool sendCommandInvoked)
-{
- // RFC 2581, pages 7 and 8: "When TCP has not received a segment for
- // more than one retransmission timeout, cwnd is reduced to the value
- // of the restart window (RW) before transmission begins.
- // For the purposes of this standard, we define RW = IW.
- // (...)
- // Using the last time a segment was received to determine whether or
- // not to decrease cwnd fails to deflate cwnd in the common case of
- // persistent HTTP connections [HTH98].
- // (...)
- // Therefore, a TCP SHOULD set cwnd to no more than RW before beginning
- // transmission if the TCP has not sent data in an interval exceeding
- // the retransmission timeout."
- if (!conn->isSendQueueEmpty()) { // do we have any data to send?
- if ((simTime() - state->time_last_data_sent) > state->rexmit_timeout) {
- // RFC 5681, page 11: "For the purposes of this standard, we define RW = min(IW,cwnd)."
- if (state->increased_IW_enabled)
- state->snd_cwnd = std::min(std::min(4 * state->snd_mss, std::max(2 * state->snd_mss, (uint32_t)4380)), state->snd_cwnd);
- else
- state->snd_cwnd = state->snd_mss;
-
- EV_INFO << "Restarting idle connection, CWND is set to " << state->snd_cwnd << "\n";
- }
- }
-
- //
- // Send window is effectively the minimum of the congestion window (cwnd)
- // and the advertised window (snd_wnd).
- //
- return conn->sendData(state->snd_cwnd);
-}
-
-void TcpBaseAlg::sendCommandInvoked()
-{
- // try sending
- sendData(true);
-}
-
-void TcpBaseAlg::receivedOutOfOrderSegment()
-{
- state->ack_now = true;
- EV_INFO << "Out-of-order segment, sending immediate ACK\n";
- conn->sendAck();
-}
-
-void TcpBaseAlg::receiveSeqChanged()
-{
- // If we send a data segment already (with the updated seqNo) there is no need to send an additional ACK
- if (state->full_sized_segment_counter == 0 && !state->ack_now && state->last_ack_sent == state->rcv_nxt && !delayedAckTimer->isScheduled()) { // ackSent?
-// tcpEV << "ACK has already been sent (possibly piggybacked on data)\n";
- }
- else {
- // RFC 2581, page 6:
- // "3.2 Fast Retransmit/Fast Recovery
- // (...)
- // In addition, a TCP receiver SHOULD send an immediate ACK
- // when the incoming segment fills in all or part of a gap in the
- // sequence space."
- if (state->lossRecovery)
- state->ack_now = true; // although not mentioned in [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 861] seems like we have to set ack_now
-
- if (!state->delayed_acks_enabled) { // delayed ACK disabled
- EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK disabled) sending ACK now\n";
- conn->sendAck();
- }
- else { // delayed ACK enabled
- if (state->ack_now) {
- EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled, but ack_now is set) sending ACK now\n";
- conn->sendAck();
- }
- // RFC 1122, page 96: "in a stream of full-sized segments there SHOULD be an ACK for at least every second segment."
- else if (state->full_sized_segment_counter >= 2) {
- EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled, but full_sized_segment_counter=" << state->full_sized_segment_counter << ") sending ACK now\n";
- conn->sendAck();
- }
- else {
- EV_INFO << "rcv_nxt changed to " << state->rcv_nxt << ", (delayed ACK enabled and full_sized_segment_counter=" << state->full_sized_segment_counter << ") scheduling ACK\n";
- if (!delayedAckTimer->isScheduled()) // schedule delayed ACK timer if not already running
- conn->scheduleAfter(DELAYED_ACK_TIMEOUT, delayedAckTimer);
- }
- }
- }
-}
-
-void TcpBaseAlg::receivedDataAck(uint32_t firstSeqAcked)
-{
- if (!state->ts_enabled) {
- // if round-trip time measurement is running, check if rtseq has been acked
- if (state->rtseq_sendtime != 0 && seqLess(state->rtseq, state->snd_una)) {
- // print value
- EV_DETAIL << "Round-trip time measured on rtseq=" << state->rtseq << ": "
- << floor((simTime() - state->rtseq_sendtime) * 1000 + 0.5) << "ms\n";
-
- rttMeasurementComplete(state->rtseq_sendtime, simTime()); // update RTT variables with new value
-
- // measurement finished
- state->rtseq_sendtime = 0;
- }
- }
-
- //
- // handling of retransmission timer: if the ACK is for the last segment sent
- // (no data in flight), cancel the timer, otherwise restart the timer
- // with the current RTO value.
- //
- if (state->snd_una == state->snd_max) {
- if (rexmitTimer->isScheduled()) {
- EV_INFO << "ACK acks all outstanding segments, cancel REXMIT timer\n";
- cancelEvent(rexmitTimer);
- }
- else
- EV_INFO << "There were no outstanding segments, nothing new in this ACK.\n";
- }
- else {
- EV_INFO << "ACK acks some but not all outstanding segments ("
- << (state->snd_max - state->snd_una) << " bytes outstanding), "
- << "restarting REXMIT timer\n";
- cancelEvent(rexmitTimer);
- startRexmitTimer();
- }
-
- //
- // handling of PERSIST timer:
- // If data sender received a zero-sized window, check retransmission timer.
- // If retransmission timer is not scheduled, start PERSIST timer if not already
- // running.
- //
- // If data sender received a non zero-sized window, check PERSIST timer.
- // If PERSIST timer is scheduled, cancel PERSIST timer.
- //
- if (state->snd_wnd == 0) { // received zero-sized window?
- if (rexmitTimer->isScheduled()) {
- if (persistTimer->isScheduled()) {
- EV_INFO << "Received zero-sized window and REXMIT timer is running therefore PERSIST timer is canceled.\n";
- cancelEvent(persistTimer);
- state->persist_factor = 0;
- }
- else
- EV_INFO << "Received zero-sized window and REXMIT timer is running therefore PERSIST timer is not started.\n";
- }
- else {
- if (!persistTimer->isScheduled()) {
- EV_INFO << "Received zero-sized window therefore PERSIST timer is started.\n";
- conn->scheduleAfter(state->persist_timeout, persistTimer);
- }
- else
- EV_INFO << "Received zero-sized window and PERSIST timer is already running.\n";
- }
- }
- else { // received non zero-sized window?
- if (persistTimer->isScheduled()) {
- EV_INFO << "Received non zero-sized window therefore PERSIST timer is canceled.\n";
- cancelEvent(persistTimer);
- state->persist_factor = 0;
- }
- }
-
- //
- // Leave congestion window management and possible sending data to
- // subclasses (e.g. TcpTahoe, TcpReno).
- //
- // That is, subclasses will redefine this method, call us, then perform
- // window adjustments and send data (if there's room in the window).
- //
-}
-
-void TcpBaseAlg::receivedDuplicateAck()
-{
- EV_INFO << "Duplicate ACK #" << state->dupacks << "\n";
-
- bool fullSegmentsOnly = state->nagle_enabled && state->snd_una != state->snd_max;
- if (state->dupacks < state->dupthresh && state->limited_transmit_enabled) // DUPTRESH = 3
- conn->sendOneNewSegment(fullSegmentsOnly, state->snd_cwnd); // RFC 3042
-
- //
- // Leave to subclasses (e.g. TcpTahoe, TcpReno) whatever they want to do
- // on duplicate Acks.
- //
- // That is, subclasses will redefine this method, call us, then perform
- // whatever action they want to do on dupAcks (e.g. retransmitting one segment).
- //
-}
-
-void TcpBaseAlg::receivedAckForDataNotYetSent(uint32_t seq)
-{
- // Note: In this case no immediate ACK will be send because not mentioned
- // in [Stevens, W.R.: TCP/IP Illustrated, Volume 2, page 861].
- // To force immediate ACK use:
-// state->ack_now = true;
-// tcpEV << "ACK acks something not yet sent, sending immediate ACK\n";
- EV_INFO << "ACK acks something not yet sent, sending ACK\n";
- conn->sendAck();
-}
-
-void TcpBaseAlg::ackSent()
-{
- state->full_sized_segment_counter = 0; // reset counter
- state->ack_now = false; // reset flag
- state->last_ack_sent = state->rcv_nxt; // update last_ack_sent, needed for TS option
- // if delayed ACK timer is running, cancel it
- if (delayedAckTimer->isScheduled())
- cancelEvent(delayedAckTimer);
-}
-
-void TcpBaseAlg::dataSent(uint32_t fromseq)
-{
- // if retransmission timer not running, schedule it
- if (!rexmitTimer->isScheduled()) {
- EV_INFO << "Starting REXMIT timer\n";
- startRexmitTimer();
- }
-
- if (!state->ts_enabled) {
- // start round-trip time measurement (if not already running)
- if (state->rtseq_sendtime == 0) {
- // remember this sequence number and when it was sent
- state->rtseq = fromseq;
- state->rtseq_sendtime = simTime();
- EV_DETAIL << "Starting rtt measurement on seq=" << state->rtseq << "\n";
- }
- }
-
- state->time_last_data_sent = simTime();
-}
-
-void TcpBaseAlg::segmentRetransmitted(uint32_t fromseq, uint32_t toseq)
-{
-}
-
-void TcpBaseAlg::restartRexmitTimer()
-{
- if (rexmitTimer->isScheduled())
- cancelEvent(rexmitTimer);
-
- startRexmitTimer();
-}
-
-bool TcpBaseAlg::shouldMarkAck()
-{
- // rfc-3168, pages 19-20:
- // When TCP receives a CE data packet at the destination end-system, the
- // TCP data receiver sets the ECN-Echo flag in the TCP header of the
- // subsequent ACK packet.
- // ...
- // After a TCP receiver sends an ACK packet with the ECN-Echo bit set,
- // that TCP receiver continues to set the ECN-Echo flag in all the ACK
- // packets it sends (whether they acknowledge CE data packets or non-CE
- // data packets) until it receives a CWR packet (a packet with the CWR
- // flag set). After the receipt of the CWR packet, acknowledgments for
- // subsequent non-CE data packets do not have the ECN-Echo flag set.
-
- if (state && state->ect) {
- if (state->gotCeIndication) {
- EV_INFO << "Received CE... ";
- if (state->ecnEchoState)
- EV_INFO << "Already in ecnEcho state\n";
- else {
- state->ecnEchoState = true;
- EV << "Entering ecnEcho state\n";
- }
- state->gotCeIndication = false;
- }
- return state->ecnEchoState;
- }
- return false;
-}
-
-void TcpBaseAlg::processEcnInEstablished()
-{
-}
-
-} // namespace tcp
-} // namespace inet
-
diff --git a/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.cc b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.cc
new file mode 100644
index 00000000000..0ceccf4e55b
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.cc
@@ -0,0 +1,281 @@
+//
+// Copyright (C) 2004 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+
+namespace inet {
+namespace tcp {
+
+void TcpClassicAlgorithmBaseStateVariables::setSendQueueLimit(uint32_t newLimit) {
+ // The initial value of ssthresh SHOULD be set arbitrarily high (e.g.,
+ // to the size of the largest possible advertised window) -> defined by sendQueueLimit
+ sendQueueLimit = newLimit;
+ ssthresh = sendQueueLimit;
+}
+
+std::string TcpClassicAlgorithmBaseStateVariables::str() const
+{
+ std::stringstream out;
+ out << TcpAlgorithmBaseStateVariables::str();
+ out << " ssthresh=" << ssthresh;
+ return out.str();
+}
+
+std::string TcpClassicAlgorithmBaseStateVariables::detailedInfo() const
+{
+ std::stringstream out;
+ out << TcpAlgorithmBaseStateVariables::detailedInfo();
+ out << "ssthresh=" << ssthresh << "\n";
+ return out.str();
+}
+
+// ---
+
+TcpClassicAlgorithmBase::TcpClassicAlgorithmBase() : TcpAlgorithmBase(),
+ state((TcpClassicAlgorithmBaseStateVariables *&)TcpAlgorithm::state)
+{
+}
+
+TcpClassicAlgorithmBase::~TcpClassicAlgorithmBase()
+{
+ delete congestionControl;
+ delete recovery;
+}
+
+void TcpClassicAlgorithmBase::initialize()
+{
+ TcpAlgorithmBase::initialize();
+ state->ssthresh = conn->getTcpMain()->par("initialSsthresh");
+}
+
+void TcpClassicAlgorithmBase::established(bool active)
+{
+ TcpAlgorithmBase::established(active);
+
+ // getRecovery() may already have lazily created the recovery for a TCP
+ // Fast Open server exchanging data in SYN_RCVD -- keep it (its SACK
+ // scoreboard context must survive the transition), don't recreate.
+ if (recovery == nullptr)
+ recovery = createRecovery();
+ congestionControl = createCongestionControl();
+}
+
+ITcpRecovery *TcpClassicAlgorithmBase::getRecovery()
+{
+ // A TCP Fast Open server exchanges data -- and can receive SACK-bearing
+ // ACKs -- while still in SYN_RCVD. Linux's TFO child socket is fully
+ // initialized at creation; mirror that by creating the recovery machinery
+ // on first use instead of only at established(). SACK negotiation is
+ // already final here (it happened on the SYN/SYN-ACK exchange), so
+ // createRecovery() picks the same implementation established() would.
+ if (recovery == nullptr && state->fastopenAccelerated)
+ recovery = createRecovery();
+ return recovery;
+}
+
+void TcpClassicAlgorithmBase::dataSent(uint32_t fromseq)
+{
+ TcpAlgorithmBase::dataSent(fromseq);
+ if (recovery != nullptr)
+ recovery->dataSent(fromseq);
+}
+
+void TcpClassicAlgorithmBase::segmentRetransmitted(uint32_t fromseq, uint32_t toseq)
+{
+ TcpAlgorithmBase::segmentRetransmitted(fromseq, toseq);
+ if (recovery != nullptr)
+ recovery->segmentRetransmitted(fromseq, toseq);
+}
+
+void TcpClassicAlgorithmBase::segmentsAcked(uint32_t fromSeq, uint32_t toSeq)
+{
+ if (recovery != nullptr)
+ recovery->segmentsAcked(fromSeq, toSeq);
+}
+
+uint32_t TcpClassicAlgorithmBase::getBytesInFlight() const
+{
+ auto rexmitQueue = conn->getRexmitQueue();
+ int64_t sentSize = state->snd_max - conn->getDataSndUna();
+ int64_t in_flight = sentSize - rexmitQueue->getSacked() - rexmitQueue->getLost() + rexmitQueue->getRetrans();
+ if (in_flight < 0)
+ in_flight = 0;
+ conn->emit(bytesInFlightSignal, in_flight);
+ return in_flight;
+}
+
+void TcpClassicAlgorithmBase::processRexmitTimer(TcpEventCode& event)
+{
+ TcpAlgorithmBase::processRexmitTimer(event);
+
+ if (event == TCP_E_ABORT)
+ return;
+
+ // Let the recovery strategy snapshot undo state / open a spurious-RTO
+ // episode before the RTO's own cwnd collapse below overwrites it.
+ if (recovery != nullptr)
+ recovery->onRexmitTimeout();
+
+ // RFC 6582, page 6:
+ // "4) Retransmit timeouts:
+ // After a retransmit timeout, record the highest sequence number
+ // transmitted in the variable "recover" and exit the Fast Recovery
+ // procedure if applicable."
+ state->recover = (state->snd_max - 1);
+ EV_INFO << "recover=" << state->recover << "\n";
+ state->lossRecovery = false;
+ state->firstPartialACK = false;
+ EV_INFO << "Loss Recovery terminated.\n";
+
+ // After REXMIT timeout TCP NewReno should start slow start with snd_cwnd = snd_mss.
+ //
+ // If calling "retransmitData();" there is no rexmit limitation (bytesToSend > snd_cwnd)
+ // therefore "sendData();" has been modified and is called to rexmit outstanding data.
+ //
+ // RFC 5681, page 8:
+ // "Furthermore, upon a timeout cwnd MUST be set to no more than the loss
+ // window, LW, which equals 1 full-sized segment (regardless of the
+ // value of IW). Therefore, after retransmitting the dropped segment
+ // the TCP sender uses the slow start algorithm to increase the window
+ // from 1 full-sized segment to the new value of ssthresh, at which
+ // point congestion avoidance again takes over."
+
+ // RFC 5681, page 7:
+ // "When a TCP sender detects segment loss using the retransmission
+ // timer and the given segment has not yet been resent by way of the
+ // retransmission timer, the value of ssthresh MUST be set to no more
+ // than the value given in equation (4):
+ //
+ // ssthresh = max (FlightSize / 2, 2*SMSS) (4)
+ //
+ // where, as discussed above, FlightSize is the amount of outstanding
+ // data in the network."
+ state->ssthresh = calculateSsthreshForRto();
+ conn->emit(ssthreshSignal, state->ssthresh);
+
+ state->snd_cwnd = calculateCwndForRto();
+ conn->emit(cwndSignal, state->snd_cwnd);
+
+ EV_INFO << "Begin Slow Start: resetting cwnd to " << state->snd_cwnd
+ << ", ssthresh=" << state->ssthresh << "\n";
+ state->afterRto = true;
+ conn->markOutstandingLostOnRto();
+ conn->retransmitOneSegment(true);
+}
+
+bool TcpClassicAlgorithmBase::isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ return recovery->isDuplicateAck(tcpHeader, payloadLength);
+}
+
+void TcpClassicAlgorithmBase::receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength)
+{
+ countDuplicateAck(tcpHeader, payloadLength);
+}
+
+void TcpClassicAlgorithmBase::processTlpAck()
+{
+ // Tail Loss Probe outcome (Linux tcp_process_tlp_ack): this ACK reached the
+ // probe's snd_max. A new-data probe acked, or a D-SACK on this ACK (meaning
+ // both the original and the probe arrived), ends the episode benignly. A
+ // RETRANSMITTED probe acked WITHOUT a D-SACK means the original tail really
+ // was lost and the probe silently repaired it, so the congestion response a
+ // fast recovery would have applied is owed -- Linux collapses
+ // tcp_init_cwnd_reduction() + tcp_end_cwnd_reduction() into a one-shot
+ // "ssthresh = cwnd/2; cwnd = ssthresh", entering no recovery episode.
+ if (state->tlpHighSeq != 0 && seqGE(state->snd_una, state->tlpHighSeq)) {
+ if (state->tlpRetrans && !state->dsackSeen) {
+ state->ssthresh = std::max(getBytesInFlight() / 2, 2 * state->snd_mss);
+ state->snd_cwnd = state->ssthresh;
+ conn->emit(ssthreshSignal, state->ssthresh);
+ conn->emit(cwndSignal, state->snd_cwnd);
+ EV_INFO << "TLP: probe repaired a real tail loss, cwnd reduced to ssthresh="
+ << state->ssthresh << "\n";
+ }
+ state->tlpHighSeq = 0;
+ }
+}
+
+void TcpClassicAlgorithmBase::receivedAckForUnackedData(uint32_t firstSeqAcked)
+{
+ processTlpAck();
+
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
+ uint32_t numBytesAcked = state->snd_una - firstSeqAcked;
+ if (state->lossRecovery)
+ recovery->receivedAckForUnackedData(numBytesAcked);
+ // an ECN-Echo that actually triggered a congestion response takes the place of
+ // this ACK's window growth (RFC 3168: "SHOULD NOT increase the congestion window
+ // in response to the receipt of an ECN-Echo ACK packet")
+ if (!state->lossRecovery && !processEce(numBytesAcked))
+ congestionControl->receivedAckForUnackedData(numBytesAcked);
+ sendData(false);
+ ensureRexmitTimerArmed();
+}
+
+void TcpClassicAlgorithmBase::receivedDuplicateAck()
+{
+ recovery->receivedDuplicateAck();
+}
+
+bool TcpClassicAlgorithmBase::processEce(uint32_t numBytesAcked)
+{
+ if (state->ect && state->gotEce) {
+ // RFC 3168, page 18
+ // "If the sender receives an ECN-Echo (ECE) ACK
+ // packet (that is, an ACK packet with the ECN-Echo flag set in the TCP
+ // header), then the sender knows that congestion was encountered in the
+ // network on the path from the sender to the receiver. The indication
+ // of congestion should be treated just as a congestion loss in non-
+ // ECN-Capable TCP. That is, the TCP source halves the congestion window
+ // "cwnd" and reduces the slow start threshold "ssthresh". The sending
+ // TCP SHOULD NOT increase the congestion window in response to the
+ // receipt of an ECN-Echo ACK packet.
+ // ...
+ // The value of the congestion window is bounded below by a value of one MSS.
+ // ...
+ // TCP should not react to congestion indications more than once every
+ // window of data (or more loosely, more than once every round-trip
+ // time). That is, the TCP sender's congestion window should be reduced
+ // only once in response to a series of dropped and/or CE packets from a
+ // single window of data. In addition, the TCP source should not decrease
+ // the slow-start threshold, ssthresh, if it has been decreased
+ // within the last round trip time."
+ if (simTime() - state->eceReactionTime > state->srtt) {
+ state->snd_cwnd = std::max(state->snd_cwnd / 2, state->snd_mss);
+ conn->emit(cwndSignal, state->snd_cwnd);
+ EV_INFO << "cwnd = cwnd / 2: received ECN-Echo ACK... new cwnd = " << state->snd_cwnd << "\n";
+
+ state->ssthresh = state->snd_cwnd;
+ conn->emit(ssthreshSignal, state->ssthresh);
+ EV_INFO << "ssthresh = cwnd: received ECN-Echo ACK... new ssthresh = " << state->ssthresh << "\n";
+
+ state->sndCwr = true;
+
+ // RFC 3168, page 18
+ // "The sending TCP MUST reset the retransmit timer on receiving
+ // the ECN-Echo packet when the congestion window is one."
+ if (state->snd_cwnd == state->snd_mss) {
+ restartRexmitTimer();
+ EV_INFO << "cwnd = 1 MSS... reset retransmit timer.\n";
+ }
+ state->eceReactionTime = simTime();
+ state->gotEce = false;
+ return true;
+ }
+ else
+ EV_INFO << "multiple ECN-Echo ACKs in less than rtt... no ECN reaction\n";
+ state->gotEce = false;
+ }
+ return false;
+}
+
+} // namespace tcp
+} // namespace inet
+
diff --git a/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h
new file mode 100644
index 00000000000..c4d2ba9ae9e
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h
@@ -0,0 +1,99 @@
+//
+// Copyright (C) 2004 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+
+#ifndef __INET_TCPCLASSICALGORITHMBASE_H
+#define __INET_TCPCLASSICALGORITHMBASE_H
+
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState_m.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Common machinery for the flavours that drive a separate loss-recovery strategy:
+ * TcpReno, TcpNewReno, DcTcp and TcpCubic. Besides holding the recovery and
+ * congestion-control objects it owns the parts of the ACK path that belong to
+ * neither -- duplicate-ACK counting, the Tail Loss Probe outcome, the RFC 3168
+ * ECN response -- and forwards send/retransmit/ack events to the recovery.
+ *
+ * TcpTahoe, TcpVegas, TcpWestwood and TcpNoCongestionControl deliberately stay on
+ * TcpAlgorithmBase: they have no recovery strategy to drive.
+ */
+class INET_API TcpClassicAlgorithmBase : public TcpAlgorithmBase
+{
+ protected:
+ TcpClassicAlgorithmBaseStateVariables *& state; // alias to TcpAlgorithm's 'state'
+
+ ITcpCongestionControl *congestionControl = nullptr;
+ ITcpRecovery *recovery = nullptr;
+
+ protected:
+ virtual ITcpRecovery *createRecovery() { return nullptr; }
+ virtual ITcpCongestionControl *createCongestionControl() { return nullptr; }
+
+ virtual TcpStateVariables *createStateVariables() override
+ {
+ return new TcpClassicAlgorithmBaseStateVariables();
+ }
+
+ virtual void established(bool active) override;
+
+ virtual void processRexmitTimer(TcpEventCode& event) override;
+
+ /** The ssthresh an expired retransmission timer collapses to (RFC 5681 eq. 4). */
+ virtual uint32_t calculateSsthreshForRto() { return std::max(getBytesInFlight() / 2, 2 * state->snd_mss); }
+
+ /** The loss window an expired retransmission timer restarts slow start from. */
+ virtual uint32_t calculateCwndForRto() { return state->snd_mss; }
+
+ /** Closes out a Tail Loss Probe episode this ACK completed (Linux tcp_process_tlp_ack). */
+ virtual void processTlpAck();
+
+ /**
+ * The flavour's ECN congestion response for this ACK. Returns true if it
+ * reduced the window, in which case the ACK's ordinary growth is skipped.
+ * The default is RFC 3168's once-per-RTT halving; DcTcp overrides it with
+ * RFC 8257's proportional reduction.
+ */
+ virtual bool processEce(uint32_t numBytesAcked);
+
+ public:
+ /** Ctor */
+ TcpClassicAlgorithmBase();
+ virtual ~TcpClassicAlgorithmBase();
+
+ virtual void initialize() override;
+
+ virtual ITcpCongestionControl *getCongestionControl() { return congestionControl; }
+ virtual ITcpRecovery *getRecovery() override;
+
+ virtual bool isDuplicateAck(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ virtual void receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
+
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
+
+ virtual void receivedDuplicateAck() override;
+
+ /** Forwarded to the recovery strategy (RFC 6937 PRR accounting, loss probes). */
+ virtual void dataSent(uint32_t fromseq) override;
+
+ /** Forwarded to the recovery strategy. */
+ virtual void segmentRetransmitted(uint32_t fromseq, uint32_t toseq) override;
+
+ /** Forwarded to the recovery strategy (pre-discard scoreboard inspection). */
+ virtual void segmentsAcked(uint32_t fromSeq, uint32_t toSeq) override;
+
+ virtual uint32_t getBytesInFlight() const override;
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif
+
diff --git a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamilyState.msg b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState.msg
similarity index 64%
rename from src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamilyState.msg
rename to src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState.msg
index c72dc4004d1..70a6e5a0048 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamilyState.msg
+++ b/src/inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState.msg
@@ -6,21 +6,21 @@
import inet.common.INETDefs;
import inet.transportlayer.tcp_common.TcpHeader;
-import inet.transportlayer.tcp.flavours.TcpBaseAlgState;
+import inet.transportlayer.tcp.flavours.TcpAlgorithmBaseState;
namespace inet::tcp;
///
-/// State variables for TcpTahoeRenoFamily.
+/// State variables for TcpClassicAlgorithmBase.
///
-struct TcpTahoeRenoFamilyStateVariables extends TcpBaseAlgStateVariables
+struct TcpClassicAlgorithmBaseStateVariables extends TcpAlgorithmBaseStateVariables
{
@descriptor(readonly);
uint32_t ssthresh; ///< slow start threshold
};
-cplusplus(TcpTahoeRenoFamilyStateVariables) {{
+cplusplus(TcpClassicAlgorithmBaseStateVariables) {{
public:
virtual std::string str() const override;
virtual std::string detailedInfo() const override;
diff --git a/src/inet/transportlayer/tcp/flavours/TcpCubic.cc b/src/inet/transportlayer/tcp/flavours/TcpCubic.cc
new file mode 100644
index 00000000000..cb3c46a6e9c
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpCubic.cc
@@ -0,0 +1,375 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#include "inet/transportlayer/tcp/flavours/TcpCubic.h"
+
+#include // max
+#include // pow
+
+#include "inet/transportlayer/tcp/Tcp.h"
+#include "inet/transportlayer/tcp/TcpSackRexmitQueue.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6582Recovery.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
+
+namespace inet {
+namespace tcp {
+
+Register_Class(TcpCubic);
+
+// RTT samples needed before the delay-increase detector may fire.
+static const uint32_t HYSTART_MIN_SAMPLES = 8;
+
+// While the window is unchanged, Linux recomputes ca->cnt at most once per
+// HZ/32; in between it reuses the cached value. Keeping that rate limit
+// matters: with a fast ACK clock the recomputation would otherwise happen many
+// times per round trip and let the curve drift away from the kernel's.
+static const double CNT_RECOMPUTE_INTERVAL = 1.0 / 32;
+
+// The Reno-emulation estimator counts in units of beta_scale/8 segments per
+// increment. Linux derives that scale from its fixed beta of 717/1024; the
+// integer division is part of the result (it evaluates to 15), so it is spelled
+// out the same way here.
+static const uint32_t BETA_SCALE = 8 * (1024 + 717) / 3 / (1024 - 717);
+
+TcpCubic::TcpCubic() : TcpClassicAlgorithmBase(),
+ state((TcpCubicStateVariables *&)TcpAlgorithm::state)
+{
+}
+
+void TcpCubic::initialize()
+{
+ TcpClassicAlgorithmBase::initialize();
+
+ state->cubic_beta = conn->getTcpMain()->par("cubicBeta");
+ state->cubic_c = conn->getTcpMain()->par("cubicC");
+ state->cubic_fast_convergence = conn->getTcpMain()->par("cubicFastConvergence");
+ state->cubic_tcp_friendliness = conn->getTcpMain()->par("cubicTcpFriendliness");
+ state->cubic_delta = conn->getTcpMain()->par("cubicDelta");
+ state->cubic_cnt_clamp = conn->getTcpMain()->par("cubicCntClamp");
+ state->hystart_enabled = conn->getTcpMain()->par("hystartEnabled");
+ state->hystart_detect = conn->getTcpMain()->par("hystartDetect");
+ state->hystart_low_window = conn->getTcpMain()->par("hystartLowWindow");
+ state->hystart_ack_delta = conn->getTcpMain()->par("hystartAckDelta");
+ state->hystart_delay_min = conn->getTcpMain()->par("hystartDelayMin");
+ state->hystart_delay_max = conn->getTcpMain()->par("hystartDelayMax");
+
+ cubicReset();
+}
+
+ITcpRecovery *TcpCubic::createRecovery()
+{
+ // SACK is orthogonal to the congestion control flavour: when the connection
+ // negotiated SACK, loss recovery must be the RFC 6675 scoreboard-based one,
+ // because the SACK receive path requires an Rfc6675Recovery.
+ if (state->sack_enabled)
+ return new Rfc6675Recovery(state, conn);
+ else
+ return new Rfc6582Recovery(state, conn);
+}
+
+void TcpCubic::cubicReset()
+{
+ state->cubic_last_max_cwnd = 0;
+ state->cubic_origin_point = 0;
+ state->cubic_K = 0;
+ state->cubic_delay_min = -1;
+ state->cubic_cnt = 0;
+ state->cubic_last_cwnd = 0;
+ state->cubic_last_time = -1;
+ state->cubic_ack_cnt = 0;
+ state->cubic_tcp_cwnd = 0;
+ state->hystart_found = false;
+}
+
+void TcpCubic::hystartReset()
+{
+ state->hystart_round_start = state->hystart_last_ack = simTime();
+ state->hystart_end_seq = state->snd_max;
+ state->hystart_curr_rtt = -1;
+ state->hystart_sample_cnt = 0;
+}
+
+void TcpCubic::processRexmitTimer(TcpEventCode& event)
+{
+ TcpClassicAlgorithmBase::processRexmitTimer(event);
+
+ if (event == TCP_E_ABORT)
+ return;
+
+ // Linux cubictcp_state(TCP_CA_Loss): a timeout invalidates the curve and the
+ // W_max memory, and slow start begins again, so HyStart starts a new round.
+ cubicReset();
+ hystartReset();
+}
+
+void TcpCubic::receivedAckForUnackedData(uint32_t firstSeqAcked)
+{
+ processTlpAck();
+
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
+
+ uint32_t numBytesAcked = state->snd_una - firstSeqAcked;
+ uint32_t numSegmentsAcked = numBytesAcked / state->snd_effmss;
+
+ processAckRttSample(firstSeqAcked);
+
+ if (state->lossRecovery)
+ recovery->receivedAckForUnackedData(numBytesAcked);
+ else if (processEce(numBytesAcked))
+ ; // an ECN-Echo reaction replaces this ACK's window growth (RFC 3168)
+ else if (state->snd_cwnd < state->ssthresh)
+ slowStart(numSegmentsAcked);
+ else if (numSegmentsAcked > 0)
+ congestionAvoidance(numSegmentsAcked);
+
+ sendData(false);
+ ensureRexmitTimerArmed();
+}
+
+void TcpCubic::slowStart(uint32_t segmentsAcked)
+{
+ // Grow by the number of segments this ACK acknowledged (RFC 3465 byte
+ // counting) rather than by one SMSS per ACK, so that a delayed-ACK receiver
+ // does not halve the slow-start rate. The cwnd-limited gate (Linux
+ // tcp_is_cwnd_limited) holds the window back while the sender is not
+ // actually filling it -- an application-limited flow must not inflate cwnd
+ // it has never used.
+ if (state->snd_effmss == 0 || (state->snd_cwnd / state->snd_effmss) < 2 * state->maxPacketsOut) {
+ state->snd_cwnd += segmentsAcked * state->snd_effmss;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ }
+
+ EV_INFO << "Slow start: cwnd=" << state->snd_cwnd << " ssthresh=" << state->ssthresh << "\n";
+}
+
+void TcpCubic::congestionAvoidance(uint32_t segmentsAcked)
+{
+ uint32_t cnt = cubicUpdate(segmentsAcked);
+
+ // Linux tcp_cong_avoid_ai. Credit accumulated while cnt was larger is spent
+ // first (one SMSS, counter cleared), and only then do this ACK's segments
+ // accumulate, with every further whole multiple of cnt buying one more SMSS.
+ if (state->cubic_cwnd_cnt >= cnt) {
+ state->cubic_cwnd_cnt = 0;
+ state->snd_cwnd += state->snd_effmss;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ EV_INFO << "Congestion avoidance: cwnd=" << state->snd_cwnd << "\n";
+ }
+
+ state->cubic_cwnd_cnt += segmentsAcked;
+
+ if (state->cubic_cwnd_cnt >= cnt) {
+ uint32_t increments = state->cubic_cwnd_cnt / cnt;
+ state->cubic_cwnd_cnt -= increments * cnt;
+ state->snd_cwnd += increments * state->snd_effmss;
+ conn->emit(cwndSignal, state->snd_cwnd);
+ EV_INFO << "Congestion avoidance: cwnd=" << state->snd_cwnd << "\n";
+ }
+ else
+ EV_INFO << "Congestion avoidance: " << state->cubic_cwnd_cnt << " of " << cnt
+ << " segments acked towards the next increment\n";
+}
+
+uint32_t TcpCubic::cubicUpdate(uint32_t segmentsAcked)
+{
+ uint32_t segCwnd = state->snd_cwnd / state->snd_effmss;
+
+ // Counted even when the recomputation below is skipped, so no acked segment
+ // is lost to the Reno-emulation estimator.
+ state->cubic_ack_cnt += segmentsAcked;
+
+ if (state->cubic_last_cwnd == segCwnd && state->cubic_last_time >= SIMTIME_ZERO
+ && simTime() - state->cubic_last_time <= CNT_RECOMPUTE_INTERVAL)
+ return std::max(state->cubic_cnt, 2u);
+
+ state->cubic_last_cwnd = segCwnd;
+ state->cubic_last_time = simTime();
+
+ if (state->cubic_epoch_start == -1) {
+ // A new epoch begins where the last window reduction left off.
+ state->cubic_epoch_start = simTime();
+ state->cubic_ack_cnt = segmentsAcked;
+ state->cubic_tcp_cwnd = segCwnd;
+
+ if (state->cubic_last_max_cwnd <= segCwnd) {
+ // Already at or above the last known W_max: the curve starts here
+ // and only probes upwards.
+ state->cubic_K = 0.0;
+ state->cubic_origin_point = segCwnd;
+ }
+ else {
+ // K is the time the curve needs to climb from the current window
+ // back to W_max, i.e. cbrt((W_max - cwnd) / C).
+ state->cubic_K = std::pow((state->cubic_last_max_cwnd - segCwnd) / state->cubic_c, 1 / 3.);
+ state->cubic_origin_point = state->cubic_last_max_cwnd;
+ }
+ }
+
+ // Aim one min-RTT ahead: the window computed now is the one that should be
+ // in effect when the next round of ACKs comes back.
+ double t = (simTime() + state->cubic_delay_min - state->cubic_epoch_start).dbl();
+ double offs = (t < state->cubic_K) ? state->cubic_K - t : t - state->cubic_K;
+ uint32_t delta = state->cubic_c * std::pow(offs, 3);
+ uint32_t target = (t < state->cubic_K) ? state->cubic_origin_point - delta : state->cubic_origin_point + delta;
+
+ // Turn the window target into an ACK count: growing by one SMSS every
+ // cwnd/(target-cwnd) acked segments traces the curve without per-ACK
+ // floating point.
+ uint32_t cnt;
+ if (target > segCwnd)
+ cnt = segCwnd / (target - segCwnd);
+ else
+ cnt = 100 * segCwnd; // beyond the target: grow only marginally
+
+ // Before the first loss there is no W_max to aim at, so cap the count to
+ // keep the window moving.
+ if (state->cubic_last_max_cwnd == 0 && cnt > state->cubic_cnt_clamp)
+ cnt = state->cubic_cnt_clamp;
+
+ if (state->cubic_tcp_friendliness) {
+ // Track the window an AIMD(1, beta) Reno flow would have reached and
+ // never grow slower than it. This is the binding term just after an RTO,
+ // where W_max was cleared and the curve alone would crawl.
+ delta = (segCwnd * BETA_SCALE) >> 3;
+ while (delta > 0 && state->cubic_ack_cnt > delta) {
+ state->cubic_ack_cnt -= delta;
+ state->cubic_tcp_cwnd++;
+ }
+
+ if (state->cubic_tcp_cwnd > segCwnd) {
+ uint32_t maxCnt = segCwnd / (state->cubic_tcp_cwnd - segCwnd);
+ if (cnt > maxCnt)
+ cnt = maxCnt;
+ }
+ }
+
+ // At most one SMSS per two acked segments, i.e. at most 1.5x per RTT.
+ state->cubic_cnt = std::max(cnt, 2u);
+ return state->cubic_cnt;
+}
+
+void TcpCubic::processAckRttSample(uint32_t firstSeqAcked)
+{
+ // Linux drives cubictcp_acked() from pkts_acked(), which gets the RTT of the
+ // ACK being processed: tcp_clean_rtx_queue times the first newly acknowledged
+ // segment against now (ack_sample::rtt_us), so EVERY ACK that advances snd_una
+ // yields a sample. That per-ACK raw value is what HyStart needs -- both its
+ // detectors count and compare individual samples, and the smoothed estimate
+ // (which moves only an eighth of the way per ACK) can neither be counted
+ // per-ACK nor rise fast enough to cross a threshold set 12.5% above the
+ // connection minimum. Deliberately kept separate from the srtt/RTO estimator,
+ // which stays on its own once-per-RTT schedule.
+ const TcpSegmentTransmitInfoList::Item *sent = state->sentInfo.get(firstSeqAcked);
+ if (sent == nullptr)
+ return;
+ // Karn's algorithm: a retransmitted segment cannot be timed, because there is
+ // no telling which copy this ACK answers. Linux discards the sample for the
+ // same reason (tcp_clean_rtx_queue only times !sacked_retrans segments when
+ // the timestamp option is not available to disambiguate).
+ if (sent->getTransmitCount() != 1)
+ return;
+ processRttSample(simTime() - sent->getFirstSentTime());
+}
+
+void TcpCubic::processRttSample(const simtime_t& rtt)
+{
+ // Right after a window reduction the samples still describe the old, larger
+ // window, so let the connection settle before trusting them.
+ if (state->cubic_epoch_start != -1 && simTime() - state->cubic_epoch_start < state->cubic_delta)
+ return;
+
+ if (state->cubic_delay_min == -1 || state->cubic_delay_min > rtt)
+ state->cubic_delay_min = rtt;
+
+ // HyStart only acts in slow start, and only once the window is large enough
+ // for the detectors to be meaningful.
+ if (state->hystart_enabled && state->snd_cwnd <= state->ssthresh
+ && state->snd_cwnd >= state->hystart_low_window * state->snd_effmss)
+ hystartUpdate(rtt);
+}
+
+void TcpCubic::hystartUpdate(const simtime_t& delay)
+{
+ if (state->hystart_found)
+ return;
+
+ // A round ends when everything that was in flight when it began has been
+ // acknowledged. Linux opens the new round here, at the TOP of hystart_update,
+ // and the placement is load-bearing: the very ACK that closes a round also
+ // provides the new round's first RTT sample, so resetting afterwards (from the
+ // slow-start path, which runs later in the ACK's processing) would throw that
+ // sample away and delay every delay check by one ACK.
+ if (seqGreater(state->snd_una, state->hystart_end_seq))
+ hystartReset();
+
+ simtime_t now = simTime();
+
+ // ACK-train detector: while ACKs keep arriving back to back, the train's
+ // length measures how much of the path is already filled; once it spans the
+ // minimum RTT, the pipe is full.
+ if (now - state->hystart_last_ack <= state->hystart_ack_delta) {
+ state->hystart_last_ack = now;
+ if (now - state->hystart_round_start > state->cubic_delay_min
+ && (state->hystart_detect & HYSTART_ACK_TRAIN))
+ state->hystart_found = true;
+ }
+
+ // Delay-increase detector: once enough samples are in, a round whose
+ // minimum RTT sits clearly above the connection minimum means a queue is
+ // building up ahead. The round minimum tracks EVERY sample, including the
+ // ones after the count is full -- Linux commit b344579ca847 ("tcp_cubic: fix
+ // spurious HYSTART_DELAY exit upon drop in min RTT") moved this out of the
+ // counting branch precisely so that a late sample which lowers the minimum is
+ // still taken into account, instead of comparing a stale round minimum
+ // against a delay_min the same ACK just pushed down.
+ if (state->hystart_curr_rtt == -1 || state->hystart_curr_rtt > delay)
+ state->hystart_curr_rtt = delay;
+
+ if (state->hystart_sample_cnt < HYSTART_MIN_SAMPLES)
+ ++state->hystart_sample_cnt;
+ else if (state->hystart_curr_rtt > state->cubic_delay_min + hystartDelayThresh(state->cubic_delay_min / 8)
+ && (state->hystart_detect & HYSTART_DELAY))
+ state->hystart_found = true;
+
+ if (state->hystart_found) {
+ // Leave slow start at the current window instead of overshooting into loss.
+ EV_INFO << "HyStart: exiting slow start, ssthresh=" << state->snd_cwnd << "\n";
+ state->ssthresh = state->snd_cwnd;
+ }
+}
+
+simtime_t TcpCubic::hystartDelayThresh(const simtime_t& t) const
+{
+ if (t > state->hystart_delay_max)
+ return state->hystart_delay_max;
+ if (t < state->hystart_delay_min)
+ return state->hystart_delay_min;
+ return t;
+}
+
+uint32_t TcpCubic::calculateSsthresh(uint32_t bytesInFlight)
+{
+ uint32_t segCwnd = state->snd_cwnd / state->snd_effmss;
+
+ EV_DETAIL << "Loss at cwnd=" << segCwnd << " segments, in flight="
+ << bytesInFlight / state->snd_effmss << " segments\n";
+
+ // Fast convergence (RFC 9438 section 4.7): a flow that lost before reaching
+ // the previous W_max is facing a new competitor, so it gives up a little
+ // more of the window to let that competitor grow.
+ if (segCwnd < state->cubic_last_max_cwnd && state->cubic_fast_convergence)
+ state->cubic_last_max_cwnd = (segCwnd * (1 + state->cubic_beta)) / 2;
+ else
+ state->cubic_last_max_cwnd = segCwnd;
+
+ state->cubic_epoch_start = -1; // the epoch ends with the reduction
+ state->cubic_last_time = -1; // every window reduction forces a cnt recomputation
+
+ return std::max(static_cast(segCwnd * state->cubic_beta), 2u) * state->snd_effmss;
+}
+
+} // namespace tcp
+} // namespace inet
diff --git a/src/inet/transportlayer/tcp/flavours/TcpCubic.h b/src/inet/transportlayer/tcp/flavours/TcpCubic.h
new file mode 100644
index 00000000000..58d1594d6a8
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpCubic.h
@@ -0,0 +1,118 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_TCPCUBIC_H
+#define __INET_TCPCUBIC_H
+
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
+#include "inet/transportlayer/tcp/flavours/TcpCubicState_m.h"
+
+namespace inet {
+namespace tcp {
+
+/**
+ * Implements CUBIC congestion control (RFC 9438), modelled on Linux's
+ * tcp_cubic.c, including the HyStart hybrid slow-start exit.
+ *
+ * CUBIC replaces Reno's linear congestion-avoidance growth with a cubic
+ * function of the time elapsed since the last window reduction. The window
+ * climbs quickly back towards W_max (the window at which loss was detected),
+ * flattens out around it, and then probes for more capacity -- so the growth
+ * rate no longer depends on the RTT, which is what makes CUBIC fair across
+ * paths of different lengths.
+ *
+ * Loss recovery is orthogonal to the growth law: this class delegates it to
+ * an RFC 6675 (SACK) or RFC 6582 (NewReno) recovery strategy, and contributes
+ * only the multiplicative decrease (cwnd * beta, with fast convergence).
+ */
+class INET_API TcpCubic : public TcpClassicAlgorithmBase
+{
+ public:
+ /** HyStart exit detectors; the hystartDetect parameter is a bitmask of these. */
+ enum HystartDetect {
+ HYSTART_ACK_TRAIN = 1, ///< ACKs arriving in a train longer than the min RTT
+ HYSTART_DELAY = 2, ///< the round's minimum RTT rising above the connection minimum
+ };
+
+ protected:
+ TcpCubicStateVariables *& state; // alias to TcpAlgorithm's 'state'
+
+ protected:
+ virtual TcpStateVariables *createStateVariables() override
+ {
+ return new TcpCubicStateVariables();
+ }
+
+ /** Picks the loss-recovery strategy matching the negotiated SACK support. */
+ virtual ITcpRecovery *createRecovery() override;
+
+ /** CUBIC collapses to beta*cwnd on a timeout, not to FlightSize/2. */
+ virtual uint32_t calculateSsthreshForRto() override { return calculateSsthresh(getBytesInFlight()); }
+
+ virtual uint32_t calculateCwndForRto() override { return state->snd_effmss; }
+
+ /** Forgets the epoch and the W_max memory (Linux bictcp_reset). */
+ virtual void cubicReset();
+
+ /** Starts a fresh HyStart round (Linux bictcp_hystart_reset). */
+ virtual void hystartReset();
+
+ /** Grows cwnd by the segments this ACK acknowledged, while cwnd-limited. */
+ virtual void slowStart(uint32_t segmentsAcked);
+
+ /** Grows cwnd along the cubic curve, one SMSS per cubic_cnt segments acked. */
+ virtual void congestionAvoidance(uint32_t segmentsAcked);
+
+ /**
+ * Recomputes cubic_cnt, the number of acked segments that must accumulate
+ * before cwnd may grow by one SMSS (Linux bictcp_update), and returns it.
+ */
+ virtual uint32_t cubicUpdate(uint32_t segmentsAcked);
+
+ /**
+ * Times the first newly acknowledged byte against now and feeds that raw
+ * per-ACK sample to the min-RTT tracker and to HyStart, the way Linux fills
+ * ack_sample::rtt_us for the flavour's pkts_acked hook. Silent when the sample
+ * would be ambiguous (a retransmitted segment).
+ */
+ virtual void processAckRttSample(uint32_t firstSeqAcked);
+
+ /** Feeds an RTT sample to the min-RTT tracker and to HyStart. */
+ virtual void processRttSample(const simtime_t& rtt);
+
+ /** Looks for the slow-start exit point (Linux hystart_update). */
+ virtual void hystartUpdate(const simtime_t& delay);
+
+ /** Clamps the delay-increase threshold into the configured range. */
+ virtual simtime_t hystartDelayThresh(const simtime_t& t) const;
+
+ public:
+ TcpCubic();
+
+ virtual void initialize() override;
+
+ virtual void processRexmitTimer(TcpEventCode& event) override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
+
+ /** TcpCubic selects RFC 6675 SACK recovery when the connection negotiated SACK. */
+ virtual bool supportsSackRecovery() const override { return true; }
+
+ /**
+ * The CUBIC multiplicative decrease: remembers the window as the new W_max
+ * (applying fast convergence), ends the epoch, and returns beta * cwnd.
+ * bytesInFlight is reported only; the reduction is taken from cwnd, as in
+ * Linux cubictcp_recalc_ssthresh.
+ */
+ virtual uint32_t calculateSsthresh(uint32_t bytesInFlight) override;
+
+ /** CUBIC reduces to cwnd*beta on fast-recovery entry, not to FlightSize/2. */
+ virtual uint32_t calculateSsthreshForFastRecovery() override { return calculateSsthresh(state->snd_cwnd); }
+};
+
+} // namespace tcp
+} // namespace inet
+
+#endif // __INET_TCPCUBIC_H
diff --git a/src/inet/transportlayer/tcp/flavours/TcpCubicState.msg b/src/inet/transportlayer/tcp/flavours/TcpCubicState.msg
new file mode 100644
index 00000000000..56bc6b70146
--- /dev/null
+++ b/src/inet/transportlayer/tcp/flavours/TcpCubicState.msg
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.INETDefs;
+import inet.transportlayer.tcp.flavours.TcpClassicAlgorithmBaseState;
+
+namespace inet::tcp;
+
+///
+/// State variables for TcpCubic (RFC 9438), including the HyStart hybrid
+/// slow-start exit. The field names follow Linux's struct bictcp so the
+/// implementation can be read next to tcp_cubic.c.
+///
+/// Window quantities are kept in SEGMENTS (not bytes): CUBIC's curve, its
+/// Reno-emulation estimate and the resulting ACK counter are all defined in
+/// packets, and rounding the window down to whole segments once, up front,
+/// keeps the integer arithmetic below identical to the kernel's.
+///
+struct TcpCubicStateVariables extends TcpClassicAlgorithmBaseStateVariables
+{
+ @descriptor(readonly);
+
+ /// CUBIC curve (Linux struct bictcp)
+ //@{
+ uint32_t cubic_last_max_cwnd = 0; ///< W_max: window (segments) where the last loss occurred
+ uint32_t cubic_origin_point = 0; ///< origin of the cubic curve (segments)
+ double cubic_K = 0; ///< seconds from the epoch start to W_max
+ simtime_t cubic_epoch_start = -1; ///< start of the current congestion-avoidance epoch; -1 = no epoch running
+ simtime_t cubic_delay_min = -1; ///< smallest RTT seen since the last reset; -1 = no sample yet
+ //@}
+
+ /// cwnd increment pacing (Linux tcp_cong_avoid_ai)
+ //@{
+ uint32_t cubic_cnt = 0; ///< ACKs (in segments) required per 1-SMSS increase
+ uint32_t cubic_cwnd_cnt = 0; ///< segments acked so far towards cubic_cnt
+ uint32_t cubic_last_cwnd = 0; ///< window (segments) when cubic_cnt was last recomputed
+ simtime_t cubic_last_time = -1; ///< when cubic_cnt was last recomputed; -1 = never
+ //@}
+
+ /// TCP friendliness: the window an AIMD(1, beta) Reno flow would have reached
+ //@{
+ uint32_t cubic_ack_cnt = 0; ///< segments acked in this epoch
+ uint32_t cubic_tcp_cwnd = 0; ///< W_est (segments)
+ //@}
+
+ /// HyStart (hybrid slow start)
+ //@{
+ simtime_t hystart_round_start = -1; ///< start of the current RTT round; -1 = unset
+ simtime_t hystart_last_ack = -1; ///< arrival of the last ACK, for the ACK-train detector; -1 = unset
+ simtime_t hystart_curr_rtt = -1; ///< smallest RTT sampled in this round; -1 = no sample yet
+ uint32_t hystart_end_seq = 0; ///< snd_max snapshot that closes the current round
+ uint32_t hystart_sample_cnt = 0; ///< RTT samples taken in this round
+ bool hystart_found = false; ///< the slow-start exit point has been detected
+ //@}
+
+ /// configuration, read from the NED parameters in TcpCubic::initialize()
+ //@{
+ double cubic_beta = 0.7; ///< multiplicative decrease factor
+ double cubic_c = 0.4; ///< scaling constant of the cubic curve
+ bool cubic_fast_convergence = true; ///< release more window when W_max shrinks
+ bool cubic_tcp_friendliness = true; ///< never grow slower than Reno would
+ simtime_t cubic_delta = 0.01; ///< settling time after a window reduction, during which RTT samples are ignored
+ uint32_t cubic_cnt_clamp = 20; ///< cap on cubic_cnt while W_max is still unknown
+ bool hystart_enabled = true; ///< look for a slow-start exit point
+ int hystart_detect = 3; ///< bitmask of HystartDetect: 1 = ACK train, 2 = delay increase
+ uint32_t hystart_low_window = 16; ///< smallest window (segments) at which HyStart acts
+ simtime_t hystart_ack_delta = 0.002; ///< largest ACK spacing still counted as a train
+ simtime_t hystart_delay_min = 0.004; ///< lower clamp on the delay-increase threshold
+ simtime_t hystart_delay_max = 1; ///< upper clamp on the delay-increase threshold
+ //@}
+};
diff --git a/src/inet/transportlayer/tcp/flavours/TcpNewReno.cc b/src/inet/transportlayer/tcp/flavours/TcpNewReno.cc
index 8366b75dec0..8dab81f4b1a 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpNewReno.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpNewReno.cc
@@ -1,339 +1,31 @@
//
-// Copyright (C) 2009 Thomas Reschka
+// Copyright (C) 2020 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
#include "inet/transportlayer/tcp/flavours/TcpNewReno.h"
-#include // min,max
-
-#include "inet/transportlayer/tcp/Tcp.h"
+#include "inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6582Recovery.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
namespace inet {
namespace tcp {
Register_Class(TcpNewReno);
-TcpNewReno::TcpNewReno() : TcpTahoeRenoFamily(),
- state((TcpNewRenoStateVariables *&)TcpAlgorithm::state)
-{
-}
-
-void TcpNewReno::recalculateSlowStartThreshold()
+ITcpRecovery *TcpNewReno::createRecovery()
{
- // RFC 2581, page 4:
- // "When a TCP sender detects segment loss using the retransmission
- // timer, the value of ssthresh MUST be set to no more than the value
- // given in equation 3:
- //
- // ssthresh = max (FlightSize / 2, 2*SMSS) (3)
- //
- // As discussed above, FlightSize is the amount of outstanding data in
- // the network."
-
- // set ssthresh to flight size / 2, but at least 2 SMSS
- // (the formula below practically amounts to ssthresh = cwnd / 2 most of the time)
- uint32_t flight_size = std::min(state->snd_cwnd, state->snd_wnd); // FIXME - Does this formula computes the amount of outstanding data?
-// uint32_t flight_size = state->snd_max - state->snd_una;
- state->ssthresh = std::max(flight_size / 2, 2 * state->snd_mss);
-
- conn->emit(ssthreshSignal, state->ssthresh);
-}
-
-void TcpNewReno::processRexmitTimer(TcpEventCode& event)
-{
- TcpTahoeRenoFamily::processRexmitTimer(event);
-
- if (event == TCP_E_ABORT)
- return;
-
- // RFC 3782, page 6:
- // "6) Retransmit timeouts:
- // After a retransmit timeout, record the highest sequence number
- // transmitted in the variable "recover" and exit the Fast Recovery
- // procedure if applicable."
- state->recover = (state->snd_max - 1);
- EV_INFO << "recover=" << state->recover << "\n";
- state->lossRecovery = false;
- state->firstPartialACK = false;
- EV_INFO << "Loss Recovery terminated.\n";
-
- // After REXMIT timeout TCP NewReno should start slow start with snd_cwnd = snd_mss.
- //
- // If calling "retransmitData();" there is no rexmit limitation (bytesToSend > snd_cwnd)
- // therefore "sendData();" has been modified and is called to rexmit outstanding data.
- //
- // RFC 2581, page 5:
- // "Furthermore, upon a timeout cwnd MUST be set to no more than the loss
- // window, LW, which equals 1 full-sized segment (regardless of the
- // value of IW). Therefore, after retransmitting the dropped segment
- // the TCP sender uses the slow start algorithm to increase the window
- // from 1 full-sized segment to the new value of ssthresh, at which
- // point congestion avoidance again takes over."
-
- // begin Slow Start (RFC 2581)
- recalculateSlowStartThreshold();
- state->snd_cwnd = state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_INFO << "Begin Slow Start: resetting cwnd to " << state->snd_cwnd
- << ", ssthresh=" << state->ssthresh << "\n";
- state->afterRto = true;
- conn->retransmitOneSegment(true);
-}
-
-void TcpNewReno::receivedDataAck(uint32_t firstSeqAcked)
-{
- TcpTahoeRenoFamily::receivedDataAck(firstSeqAcked);
-
- // RFC 3782, page 5:
- // "5) When an ACK arrives that acknowledges new data, this ACK could be
- // the acknowledgment elicited by the retransmission from step 2, or
- // elicited by a later retransmission.
- //
- // Full acknowledgements:
- // If this ACK acknowledges all of the data up to and including
- // "recover", then the ACK acknowledges all the intermediate
- // segments sent between the original transmission of the lost
- // segment and the receipt of the third duplicate ACK. Set cwnd to
- // either (1) min (ssthresh, FlightSize + SMSS) or (2) ssthresh,
- // where ssthresh is the value set in step 1; this is termed
- // "deflating" the window. (We note that "FlightSize" in step 1
- // referred to the amount of data outstanding in step 1, when Fast
- // Recovery was entered, while "FlightSize" in step 5 refers to the
- // amount of data outstanding in step 5, when Fast Recovery is
- // exited.) If the second option is selected, the implementation is
- // encouraged to take measures to avoid a possible burst of data, in
- // case the amount of data outstanding in the network is much less
- // than the new congestion window allows. A simple mechanism is to
- // limit the number of data packets that can be sent in response to
- // a single acknowledgement; this is known as "maxburst_" in the NS
- // simulator. Exit the Fast Recovery procedure."
- if (state->lossRecovery) {
- if (seqGE(state->snd_una - 1, state->recover)) {
- // Exit Fast Recovery: deflating cwnd
- //
- // option (1): set cwnd to min (ssthresh, FlightSize + SMSS)
- uint32_t flight_size = state->snd_max - state->snd_una;
- state->snd_cwnd = std::min(state->ssthresh, flight_size + state->snd_mss);
- EV_INFO << "Fast Recovery - Full ACK received: Exit Fast Recovery, setting cwnd to " << state->snd_cwnd << "\n";
- // option (2): set cwnd to ssthresh
-// state->snd_cwnd = state->ssthresh;
-// tcpEV << "Fast Recovery - Full ACK received: Exit Fast Recovery, setting cwnd to ssthresh=" << state->ssthresh << "\n";
- // TODO - If the second option (2) is selected, take measures to avoid a possible burst of data (maxburst)!
- conn->emit(cwndSignal, state->snd_cwnd);
-
- state->lossRecovery = false;
- state->firstPartialACK = false;
- EV_INFO << "Loss Recovery terminated.\n";
- }
- else {
- // RFC 3782, page 5:
- // "Partial acknowledgements:
- // If this ACK does *not* acknowledge all of the data up to and
- // including "recover", then this is a partial ACK. In this case,
- // retransmit the first unacknowledged segment. Deflate the
- // congestion window by the amount of new data acknowledged by the
- // cumulative acknowledgement field. If the partial ACK
- // acknowledges at least one SMSS of new data, then add back SMSS
- // bytes to the congestion window. As in Step 3, this artificially
- // inflates the congestion window in order to reflect the additional
- // segment that has left the network. Send a new segment if
- // permitted by the new value of cwnd. This "partial window
- // deflation" attempts to ensure that, when Fast Recovery eventually
- // ends, approximately ssthresh amount of data will be outstanding
- // in the network. Do not exit the Fast Recovery procedure (i.e.,
- // if any duplicate ACKs subsequently arrive, execute Steps 3 and 4
- // above).
- //
- // For the first partial ACK that arrives during Fast Recovery, also
- // reset the retransmit timer. Timer management is discussed in
- // more detail in Section 4."
-
- EV_INFO << "Fast Recovery - Partial ACK received: retransmitting the first unacknowledged segment\n";
- // retransmit first unacknowledged segment
- conn->retransmitOneSegment(false);
-
- // deflate cwnd by amount of new data acknowledged by cumulative acknowledgement field
- state->snd_cwnd -= state->snd_una - firstSeqAcked;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_INFO << "Fast Recovery: deflating cwnd by amount of new data acknowledged, new cwnd=" << state->snd_cwnd << "\n";
-
- // if the partial ACK acknowledges at least one SMSS of new data, then add back SMSS bytes to the cwnd
- if (state->snd_una - firstSeqAcked >= state->snd_mss) {
- state->snd_cwnd += state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << "Fast Recovery: inflating cwnd by SMSS, new cwnd=" << state->snd_cwnd << "\n";
- }
-
- // try to send a new segment if permitted by the new value of cwnd
- sendData(false);
-
- // reset REXMIT timer for the first partial ACK that arrives during Fast Recovery
- if (state->lossRecovery) {
- if (!state->firstPartialACK) {
- state->firstPartialACK = true;
- EV_DETAIL << "First partial ACK arrived during recovery, restarting REXMIT timer.\n";
- restartRexmitTimer();
- }
- }
- }
- }
- else {
- //
- // Perform slow start and congestion avoidance.
- //
- if (state->snd_cwnd < state->ssthresh) {
- EV_DETAIL << "cwnd <= ssthresh: Slow Start: increasing cwnd by SMSS bytes to ";
-
- // perform Slow Start. RFC 2581: "During slow start, a TCP increments cwnd
- // by at most SMSS bytes for each ACK received that acknowledges new data."
- state->snd_cwnd += state->snd_mss;
-
- // Note: we could increase cwnd based on the number of bytes being
- // acknowledged by each arriving ACK, rather than by the number of ACKs
- // that arrive. This is called "Appropriate Byte Counting" (ABC) and is
- // described in RFC 3465. This RFC is experimental and probably not
- // implemented in real-life TCPs, hence it's commented out. Also, the ABC
- // RFC would require other modifications as well in addition to the
- // two lines below.
- //
-// int bytesAcked = state->snd_una - firstSeqAcked;
-// state->snd_cwnd += bytesAcked * state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << "cwnd=" << state->snd_cwnd << "\n";
- }
- else {
- // perform Congestion Avoidance (RFC 2581)
- uint32_t incr = state->snd_mss * state->snd_mss / state->snd_cwnd;
-
- if (incr == 0)
- incr = 1;
-
- state->snd_cwnd += incr;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- //
- // Note: some implementations use extra additive constant mss / 8 here
- // which is known to be incorrect (RFC 2581 p5)
- //
- // Note 2: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
- // would require maintaining a bytes_acked variable here which we don't do
- //
-
- EV_DETAIL << "cwnd > ssthresh: Congestion Avoidance: increasing cwnd linearly, to " << state->snd_cwnd << "\n";
- }
-
- // RFC 3782, page 13:
- // "When not in Fast Recovery, the value of the state variable "recover"
- // should be pulled along with the value of the state variable for
- // acknowledgments (typically, "snd_una") so that, when large amounts of
- // data have been sent and acked, the sequence space does not wrap and
- // falsely indicate that Fast Recovery should not be entered (Section 3,
- // step 1, last paragraph)."
- state->recover = (state->snd_una - 2);
- }
-
- sendData(false);
+ if (state->sack_enabled)
+ return new Rfc6675Recovery(state, conn);
+ else
+ return new Rfc6582Recovery(state, conn);
}
-void TcpNewReno::receivedDuplicateAck()
+ITcpCongestionControl *TcpNewReno::createCongestionControl()
{
- TcpTahoeRenoFamily::receivedDuplicateAck();
-
- if (state->dupacks == state->dupthresh) {
- if (!state->lossRecovery) {
- // RFC 3782, page 4:
- // "1) Three duplicate ACKs:
- // When the third duplicate ACK is received and the sender is not
- // already in the Fast Recovery procedure, check to see if the
- // Cumulative Acknowledgement field covers more than "recover". If
- // so, go to Step 1A. Otherwise, go to Step 1B."
- //
- // RFC 3782, page 6:
- // "Step 1 specifies a check that the Cumulative Acknowledgement field
- // covers more than "recover". Because the acknowledgement field
- // contains the sequence number that the sender next expects to receive,
- // the acknowledgement "ack_number" covers more than "recover" when:
- // ack_number - 1 > recover;"
- if (state->snd_una - 1 > state->recover) {
- EV_INFO << "NewReno on dupAcks == DUPTHRESH(=" << state->dupthresh << ": perform Fast Retransmit, and enter Fast Recovery:";
-
- // RFC 3782, page 4:
- // "1A) Invoking Fast Retransmit:
- // If so, then set ssthresh to no more than the value given in
- // equation 1 below. (This is equation 3 from [RFC2581]).
- // ssthresh = max (FlightSize / 2, 2*SMSS) (1)
- // In addition, record the highest sequence number transmitted in
- // the variable "recover", and go to Step 2."
- recalculateSlowStartThreshold();
- state->recover = (state->snd_max - 1);
- state->firstPartialACK = false;
- state->lossRecovery = true;
- EV_INFO << " set recover=" << state->recover;
-
- // RFC 3782, page 4:
- // "2) Entering Fast Retransmit:
- // Retransmit the lost segment and set cwnd to ssthresh plus 3 * SMSS.
- // This artificially "inflates" the congestion window by the number
- // of segments (three) that have left the network and the receiver
- // has buffered."
- state->snd_cwnd = state->ssthresh + 3 * state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << " , cwnd=" << state->snd_cwnd << ", ssthresh=" << state->ssthresh << "\n";
- conn->retransmitOneSegment(false);
-
- // RFC 3782, page 5:
- // "4) Fast Recovery, continued:
- // Transmit a segment, if allowed by the new value of cwnd and the
- // receiver's advertised window."
- sendData(false);
- }
- else {
- EV_INFO << "NewReno on dupAcks == DUPTHRESH(=" << state->dupthresh << ": not invoking Fast Retransmit and Fast Recovery\n";
-
- // RFC 3782, page 4:
- // "1B) Not invoking Fast Retransmit:
- // Do not enter the Fast Retransmit and Fast Recovery procedure. In
- // particular, do not change ssthresh, do not go to Step 2 to
- // retransmit the "lost" segment, and do not execute Step 3 upon
- // subsequent duplicate ACKs."
- }
- }
- EV_INFO << "NewReno on dupAcks == DUPTHRESH(=" << state->dupthresh << ": TCP is already in Fast Recovery procedure\n";
- }
- else if (state->dupacks > state->dupthresh) {
- if (state->lossRecovery) {
- // RFC 3782, page 4:
- // "3) Fast Recovery:
- // For each additional duplicate ACK received while in Fast
- // Recovery, increment cwnd by SMSS. This artificially inflates the
- // congestion window in order to reflect the additional segment that
- // has left the network."
- state->snd_cwnd += state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << "NewReno on dupAcks > DUPTHRESH(=" << state->dupthresh << ": Fast Recovery: inflating cwnd by SMSS, new cwnd=" << state->snd_cwnd << "\n";
-
- // RFC 3782, page 5:
- // "4) Fast Recovery, continued:
- // Transmit a segment, if allowed by the new value of cwnd and the
- // receiver's advertised window."
- sendData(false);
- }
- }
+ return new Rfc5681CongestionControl(state, conn);
}
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpNewReno.h b/src/inet/transportlayer/tcp/flavours/TcpNewReno.h
index c8f8711bf39..c5527522e03 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpNewReno.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpNewReno.h
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2009 Thomas Reschka
+// Copyright (C) 2020 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
@@ -7,45 +7,23 @@
#ifndef __INET_TCPNEWRENO_H
#define __INET_TCPNEWRENO_H
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
namespace inet {
namespace tcp {
/**
- * State variables for TcpNewReno.
+ * Implements RFC 6582: The NewReno Modification to TCP's Fast Recovery Algorithm.
*/
-typedef TcpTahoeRenoFamilyStateVariables TcpNewRenoStateVariables;
-
-/**
- * Implements TCP NewReno.
- */
-class INET_API TcpNewReno : public TcpTahoeRenoFamily
+class INET_API TcpNewReno : public TcpClassicAlgorithmBase
{
protected:
- TcpNewRenoStateVariables *& state; // alias to TcpAlgorithm's 'state'
-
- /** Create and return a TcpNewRenoStateVariables object. */
- virtual TcpStateVariables *createStateVariables() override
- {
- return new TcpNewRenoStateVariables();
- }
-
- /** Utility function to recalculate ssthresh */
- virtual void recalculateSlowStartThreshold();
-
- /** Redefine what should happen on retransmission */
- virtual void processRexmitTimer(TcpEventCode& event) override;
+ virtual ITcpRecovery *createRecovery() override;
+ virtual ITcpCongestionControl *createCongestionControl() override;
public:
- /** Ctor */
- TcpNewReno();
-
- /** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
-
- /** Redefine what should happen when dupAck was received, to add congestion window management */
- virtual void receivedDuplicateAck() override;
+ /** TcpNewReno selects RFC 6675 SACK recovery when the connection negotiated SACK. */
+ virtual bool supportsSackRecovery() const override { return true; }
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.cc b/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.cc
index f6119c0257a..de77da9947e 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.cc
@@ -14,14 +14,14 @@ namespace tcp {
Register_Class(TcpNoCongestionControl);
-TcpNoCongestionControl::TcpNoCongestionControl() : TcpBaseAlg(),
+TcpNoCongestionControl::TcpNoCongestionControl() : TcpAlgorithmBase(),
state((TcpNoCongestionControlStateVariables *&)TcpAlgorithm::state)
{
}
void TcpNoCongestionControl::initialize()
{
- TcpBaseAlg::initialize();
+ TcpAlgorithmBase::initialize();
// set congestion window to a practically infinite value
state->snd_cwnd = 0x7fffffff;
@@ -63,7 +63,7 @@ bool TcpNoCongestionControl::sendData(bool sendCommandInvoked)
void TcpNoCongestionControl::processRexmitTimer(TcpEventCode& event)
{
- TcpBaseAlg::processRexmitTimer(event);
+ TcpAlgorithmBase::processRexmitTimer(event);
if (event == TCP_E_ABORT)
return;
@@ -74,9 +74,9 @@ void TcpNoCongestionControl::processRexmitTimer(TcpEventCode& event)
ASSERT(state->snd_cwnd == 0x7fffffff);
}
-void TcpNoCongestionControl::receivedDataAck(uint32_t firstSeqAcked)
+void TcpNoCongestionControl::receivedAckForUnackedData(uint32_t firstSeqAcked)
{
- TcpBaseAlg::receivedDataAck(firstSeqAcked);
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
// ack may have freed up some room in the window, try sending
sendData(false);
diff --git a/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.h b/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.h
index a2b272c4407..3e55261844d 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpNoCongestionControl.h
@@ -8,7 +8,7 @@
#ifndef __INET_TCPNOCONGESTIONCONTROL_H
#define __INET_TCPNOCONGESTIONCONTROL_H
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
namespace inet {
namespace tcp {
@@ -16,13 +16,13 @@ namespace tcp {
/**
* State variables for TcpNoCongestionControl.
*/
-typedef TcpBaseAlgStateVariables TcpNoCongestionControlStateVariables;
+typedef TcpAlgorithmBaseStateVariables TcpNoCongestionControlStateVariables;
/**
* TCP with no congestion control (i.e. congestion window kept very large).
* Can be used to demonstrate effect of lack of congestion control.
*/
-class INET_API TcpNoCongestionControl : public TcpBaseAlg
+class INET_API TcpNoCongestionControl : public TcpAlgorithmBase
{
protected:
TcpNoCongestionControlStateVariables *& state; // alias to TcpAlgorithm's 'state'
@@ -44,7 +44,7 @@ class INET_API TcpNoCongestionControl : public TcpBaseAlg
virtual void initialize() override;
/** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
virtual void established(bool active) override;
diff --git a/src/inet/transportlayer/tcp/flavours/TcpReno.cc b/src/inet/transportlayer/tcp/flavours/TcpReno.cc
index 30bc16a65ba..3963e49abab 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpReno.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpReno.cc
@@ -1,350 +1,36 @@
//
-// Copyright (C) 2004-2005 OpenSim Ltd.
-// Copyright (C) 2009 Thomas Reschka
+// Copyright (C) 2020 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
#include "inet/transportlayer/tcp/flavours/TcpReno.h"
-#include // min,max
-
-#include "inet/transportlayer/tcp/Tcp.h"
+#include "inet/transportlayer/tcp/flavours/Rfc5681CongestionControl.h"
+#include "inet/transportlayer/tcp/flavours/Rfc5681Recovery.h"
+#include "inet/transportlayer/tcp/flavours/Rfc6675Recovery.h"
namespace inet {
namespace tcp {
Register_Class(TcpReno);
-TcpReno::TcpReno() : TcpTahoeRenoFamily(),
- state((TcpRenoStateVariables *&)TcpAlgorithm::state)
-{
-}
-
-void TcpReno::recalculateSlowStartThreshold()
-{
- // RFC 2581, page 4:
- // "When a TCP sender detects segment loss using the retransmission
- // timer, the value of ssthresh MUST be set to no more than the value
- // given in equation 3:
- //
- // ssthresh = max (FlightSize / 2, 2*SMSS) (3)
- //
- // As discussed above, FlightSize is the amount of outstanding data in
- // the network."
-
- // set ssthresh to flight size / 2, but at least 2 SMSS
- // (the formula below practically amounts to ssthresh = cwnd / 2 most of the time)
- uint32_t flight_size = std::min(state->snd_cwnd, state->snd_wnd); // FIXME - Does this formula computes the amount of outstanding data?
-// uint32_t flight_size = state->snd_max - state->snd_una;
- state->ssthresh = std::max(flight_size / 2, 2 * state->snd_mss);
-
- conn->emit(ssthreshSignal, state->ssthresh);
-}
-
-void TcpReno::processRexmitTimer(TcpEventCode& event)
-{
- TcpTahoeRenoFamily::processRexmitTimer(event);
-
- if (event == TCP_E_ABORT)
- return;
-
- // After REXMIT timeout TCP Reno should start slow start with snd_cwnd = snd_mss.
- //
- // If calling "retransmitData();" there is no rexmit limitation (bytesToSend > snd_cwnd)
- // therefore "sendData();" has been modified and is called to rexmit outstanding data.
- //
- // RFC 2581, page 5:
- // "Furthermore, upon a timeout cwnd MUST be set to no more than the loss
- // window, LW, which equals 1 full-sized segment (regardless of the
- // value of IW). Therefore, after retransmitting the dropped segment
- // the TCP sender uses the slow start algorithm to increase the window
- // from 1 full-sized segment to the new value of ssthresh, at which
- // point congestion avoidance again takes over."
-
- // begin Slow Start (RFC 2581)
- recalculateSlowStartThreshold();
- state->snd_cwnd = state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_INFO << "Begin Slow Start: resetting cwnd to " << state->snd_cwnd
- << ", ssthresh=" << state->ssthresh << "\n";
-
- state->afterRto = true;
-
- conn->retransmitOneSegment(true);
-}
-
-void TcpReno::receivedDataAck(uint32_t firstSeqAcked)
+ITcpRecovery *TcpReno::createRecovery()
{
- TcpTahoeRenoFamily::receivedDataAck(firstSeqAcked);
-
- if (state->dupacks >= state->dupthresh) {
- //
- // Perform Fast Recovery: set cwnd to ssthresh (deflating the window).
- //
- EV_INFO << "Fast Recovery: setting cwnd to ssthresh=" << state->ssthresh << "\n";
- state->snd_cwnd = state->ssthresh;
-
- conn->emit(cwndSignal, state->snd_cwnd);
- }
- else {
- bool performSsCa = true; // Stands for: "perform slow start and congestion avoidance"
- if (state && state->ect && state->gotEce) {
- // halve cwnd and reduce ssthresh and do not increase cwnd (rfc-3168, page 18):
- // If the sender receives an ECN-Echo (ECE) ACK
- // packet (that is, an ACK packet with the ECN-Echo flag set in the TCP
- // header), then the sender knows that congestion was encountered in the
- // network on the path from the sender to the receiver. The indication
- // of congestion should be treated just as a congestion loss in non-
- // ECN-Capable TCP. That is, the TCP source halves the congestion window
- // "cwnd" and reduces the slow start threshold "ssthresh". The sending
- // TCP SHOULD NOT increase the congestion window in response to the
- // receipt of an ECN-Echo ACK packet.
- // ...
- // The value of the congestion window is bounded below by a value of one MSS.
- // ...
- // TCP should not react to congestion indications more than once every
- // window of data (or more loosely, more than once every round-trip
- // time). That is, the TCP sender's congestion window should be reduced
- // only once in response to a series of dropped and/or CE packets from a
- // single window of data. In addition, the TCP source should not decrease
- // the slow-start threshold, ssthresh, if it has been decreased
- // within the last round trip time.
- if (simTime() - state->eceReactionTime > state->srtt) {
- state->ssthresh = state->snd_cwnd / 2;
- state->snd_cwnd = std::max(state->snd_cwnd / 2, uint32_t(1));
- state->sndCwr = true;
- performSsCa = false;
- EV_INFO << "ssthresh = cwnd/2: received ECN-Echo ACK... new ssthresh = "
- << state->ssthresh << "\n";
- EV_INFO << "cwnd /= 2: received ECN-Echo ACK... new cwnd = "
- << state->snd_cwnd << "\n";
-
- // rfc-3168 page 18:
- // The sending TCP MUST reset the retransmit timer on receiving
- // the ECN-Echo packet when the congestion window is one.
- if (state->snd_cwnd == 1) {
- restartRexmitTimer();
- EV_INFO << "cwnd = 1... reset retransmit timer.\n";
- }
- state->eceReactionTime = simTime();
- conn->emit(cwndSignal, state->snd_cwnd);
- conn->emit(ssthreshSignal, state->ssthresh);
- }
- else
- EV_INFO << "multiple ECN-Echo ACKs in less than rtt... no ECN reaction\n";
- state->gotEce = false;
- }
- if (performSsCa) {
- // If ECN is not enabled or if ECN is enabled and received multiple ECE-Acks in
- // less than RTT, then perform slow start and congestion avoidance.
-
- if (state->snd_cwnd < state->ssthresh) {
- EV_INFO << "cwnd <= ssthresh: Slow Start: increasing cwnd by one SMSS bytes to ";
-
- // perform Slow Start. RFC 2581: "During slow start, a TCP increments cwnd
- // by at most SMSS bytes for each ACK received that acknowledges new data."
- state->snd_cwnd += state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
- conn->emit(ssthreshSignal, state->ssthresh);
-
- EV_INFO << "cwnd=" << state->snd_cwnd << "\n";
- }
- else {
- // perform Congestion Avoidance (RFC 2581)
- uint32_t incr = state->snd_mss * state->snd_mss / state->snd_cwnd;
-
- if (incr == 0)
- incr = 1;
-
- state->snd_cwnd += incr;
-
- conn->emit(cwndSignal, state->snd_cwnd);
- conn->emit(ssthreshSignal, state->ssthresh);
-
- //
- // Note: some implementations use extra additive constant mss / 8 here
- // which is known to be incorrect (RFC 2581 p5)
- //
- // Note 2: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
- // would require maintaining a bytes_acked variable here which we don't do
- //
-
- EV_INFO << "cwnd > ssthresh: Congestion Avoidance: increasing cwnd linearly, to " << state->snd_cwnd << "\n";
- }
- }
- }
-
- if (state->sack_enabled && state->lossRecovery) {
- // RFC 3517, page 7: "Once a TCP is in the loss recovery phase the following procedure MUST
- // be used for each arriving ACK:
- //
- // (A) An incoming cumulative ACK for a sequence number greater than
- // RecoveryPoint signals the end of loss recovery and the loss
- // recovery phase MUST be terminated. Any information contained in
- // the scoreboard for sequence numbers greater than the new value of
- // HighACK SHOULD NOT be cleared when leaving the loss recovery
- // phase."
- if (seqGE(state->snd_una, state->recoveryPoint)) {
- EV_INFO << "Loss Recovery terminated.\n";
- state->lossRecovery = false;
- }
- // RFC 3517, page 7: "(B) Upon receipt of an ACK that does not cover RecoveryPoint the
- // following actions MUST be taken:
- //
- // (B.1) Use Update () to record the new SACK information conveyed
- // by the incoming ACK.
- //
- // (B.2) Use SetPipe () to re-calculate the number of octets still
- // in the network."
- else {
- // update of scoreboard (B.1) has already be done in readHeaderOptions()
- conn->setPipe();
-
- // RFC 3517, page 7: "(C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more
- // segments as follows:"
- if (((int)state->snd_cwnd - (int)state->pipe) >= (int)state->snd_mss) // Note: Typecast needed to avoid prohibited transmissions
- conn->sendDataDuringLossRecoveryPhase(state->snd_cwnd);
- }
- }
-
- // RFC 3517, pages 7 and 8: "5.1 Retransmission Timeouts
- // (...)
- // If there are segments missing from the receiver's buffer following
- // processing of the retransmitted segment, the corresponding ACK will
- // contain SACK information. In this case, a TCP sender SHOULD use this
- // SACK information when determining what data should be sent in each
- // segment of the slow start. The exact algorithm for this selection is
- // not specified in this document (specifically NextSeg () is
- // inappropriate during slow start after an RTO). A relatively
- // straightforward approach to "filling in" the sequence space reported
- // as missing should be a reasonable approach."
- sendData(false);
+ // SACK is orthogonal to the congestion control flavour: when the connection
+ // negotiated SACK, loss recovery must be the RFC 6675 scoreboard-based one,
+ // because the SACK receive path (processSACKOption/addSacks) requires an
+ // Rfc6675Recovery. TcpNewReno already selects this way; without it, enabling
+ // sackSupport on a TcpReno connection aborts on the first SACK block.
+ if (state->sack_enabled)
+ return new Rfc6675Recovery(state, conn);
+ else
+ return new Rfc5681Recovery(state, conn);
}
-void TcpReno::receivedDuplicateAck()
+ITcpCongestionControl *TcpReno::createCongestionControl()
{
- TcpTahoeRenoFamily::receivedDuplicateAck();
-
- if (state->dupacks == state->dupthresh) {
- EV_INFO << "Reno on dupAcks == DUPTHRESH(=" << state->dupthresh << ": perform Fast Retransmit, and enter Fast Recovery:";
-
- if (state->sack_enabled) {
- // RFC 3517, page 6: "When a TCP sender receives the duplicate ACK corresponding to
- // DupThresh ACKs, the scoreboard MUST be updated with the new SACK
- // information (via Update ()). If no previous loss event has occurred
- // on the connection or the cumulative acknowledgment point is beyond
- // the last value of RecoveryPoint, a loss recovery phase SHOULD be
- // initiated, per the fast retransmit algorithm outlined in [RFC2581].
- // The following steps MUST be taken:
- //
- // (1) RecoveryPoint = HighData
- //
- // When the TCP sender receives a cumulative ACK for this data octet
- // the loss recovery phase is terminated."
-
- // RFC 3517, page 8: "If an RTO occurs during loss recovery as specified in this document,
- // RecoveryPoint MUST be set to HighData. Further, the new value of
- // RecoveryPoint MUST be preserved and the loss recovery algorithm
- // outlined in this document MUST be terminated. In addition, a new
- // recovery phase (as described in section 5) MUST NOT be initiated
- // until HighACK is greater than or equal to the new value of
- // RecoveryPoint."
- if (state->recoveryPoint == 0 || seqGE(state->snd_una, state->recoveryPoint)) { // HighACK = snd_una
- state->recoveryPoint = state->snd_max; // HighData = snd_max
- state->lossRecovery = true;
- EV_DETAIL << " recoveryPoint=" << state->recoveryPoint;
- }
- }
- // RFC 2581, page 5:
- // "After the fast retransmit algorithm sends what appears to be the
- // missing segment, the "fast recovery" algorithm governs the
- // transmission of new data until a non-duplicate ACK arrives.
- // (...) the TCP sender can continue to transmit new
- // segments (although transmission must continue using a reduced cwnd)."
-
- // enter Fast Recovery
- recalculateSlowStartThreshold();
- // "set cwnd to ssthresh plus 3 * SMSS." (RFC 2581)
- state->snd_cwnd = state->ssthresh + 3 * state->snd_mss; // 20051129 (1)
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << " set cwnd=" << state->snd_cwnd << ", ssthresh=" << state->ssthresh << "\n";
-
- // Fast Retransmission: retransmit missing segment without waiting
- // for the REXMIT timer to expire
- conn->retransmitOneSegment(false);
-
- // Do not restart REXMIT timer.
- // Note: Restart of REXMIT timer on retransmission is not part of RFC 2581, however optional in RFC 3517 if sent during recovery.
- // Resetting the REXMIT timer is discussed in RFC 2582/3782 (NewReno) and RFC 2988.
-
- if (state->sack_enabled) {
- // RFC 3517, page 7: "(4) Run SetPipe ()
- //
- // Set a "pipe" variable to the number of outstanding octets
- // currently "in the pipe"; this is the data which has been sent by
- // the TCP sender but for which no cumulative or selective
- // acknowledgment has been received and the data has not been
- // determined to have been dropped in the network. It is assumed
- // that the data is still traversing the network path."
- conn->setPipe();
- // RFC 3517, page 7: "(5) In order to take advantage of potential additional available
- // cwnd, proceed to step (C) below."
- if (state->lossRecovery) {
- // RFC 3517, page 9: "Therefore we give implementers the latitude to use the standard
- // [RFC2988] style RTO management or, optionally, a more careful variant
- // that re-arms the RTO timer on each retransmission that is sent during
- // recovery MAY be used. This provides a more conservative timer than
- // specified in [RFC2988], and so may not always be an attractive
- // alternative. However, in some cases it may prevent needless
- // retransmissions, go-back-N transmission and further reduction of the
- // congestion window."
- // Note: Restart of REXMIT timer on retransmission is not part of RFC 2581, however optional in RFC 3517 if sent during recovery.
- EV_INFO << "Retransmission sent during recovery, restarting REXMIT timer.\n";
- restartRexmitTimer();
-
- // RFC 3517, page 7: "(C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more
- // segments as follows:"
- if (((int)state->snd_cwnd - (int)state->pipe) >= (int)state->snd_mss) // Note: Typecast needed to avoid prohibited transmissions
- conn->sendDataDuringLossRecoveryPhase(state->snd_cwnd);
- }
- }
-
- // try to transmit new segments (RFC 2581)
- sendData(false);
- }
- else if (state->dupacks > state->dupthresh) {
- //
- // Reno: For each additional duplicate ACK received, increment cwnd by SMSS.
- // This artificially inflates the congestion window in order to reflect the
- // additional segment that has left the network
- //
- state->snd_cwnd += state->snd_mss;
- EV_DETAIL << "Reno on dupAcks > DUPTHRESH(=" << state->dupthresh << ": Fast Recovery: inflating cwnd by SMSS, new cwnd=" << state->snd_cwnd << "\n";
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- // Note: Steps (A) - (C) of RFC 3517, page 7 ("Once a TCP is in the loss recovery phase the following procedure MUST be used for each arriving ACK")
- // should not be used here!
-
- // RFC 3517, pages 7 and 8: "5.1 Retransmission Timeouts
- // (...)
- // If there are segments missing from the receiver's buffer following
- // processing of the retransmitted segment, the corresponding ACK will
- // contain SACK information. In this case, a TCP sender SHOULD use this
- // SACK information when determining what data should be sent in each
- // segment of the slow start. The exact algorithm for this selection is
- // not specified in this document (specifically NextSeg () is
- // inappropriate during slow start after an RTO). A relatively
- // straightforward approach to "filling in" the sequence space reported
- // as missing should be a reasonable approach."
- sendData(false);
- }
+ return new Rfc5681CongestionControl(state, conn);
}
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpReno.h b/src/inet/transportlayer/tcp/flavours/TcpReno.h
index e4d9c53af84..aa68278963e 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpReno.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpReno.h
@@ -1,52 +1,29 @@
//
-// Copyright (C) 2004 OpenSim Ltd.
+// Copyright (C) 2020 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
-
#ifndef __INET_TCPRENO_H
#define __INET_TCPRENO_H
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBase.h"
namespace inet {
namespace tcp {
/**
- * State variables for TcpReno.
+ * Implements RFC 6582: The NewReno Modification to TCP's Fast Recovery Algorithm.
*/
-typedef TcpTahoeRenoFamilyStateVariables TcpRenoStateVariables;
-
-/**
- * Implements TCP Reno.
- */
-class INET_API TcpReno : public TcpTahoeRenoFamily
+class INET_API TcpReno : public TcpClassicAlgorithmBase
{
protected:
- TcpRenoStateVariables *& state; // alias to TCLAlgorithm's 'state'
-
- /** Create and return a TcpRenoStateVariables object. */
- virtual TcpStateVariables *createStateVariables() override
- {
- return new TcpRenoStateVariables();
- }
-
- /** Utility function to recalculate ssthresh */
- virtual void recalculateSlowStartThreshold();
-
- /** Redefine what should happen on retransmission */
- virtual void processRexmitTimer(TcpEventCode& event) override;
+ virtual ITcpRecovery *createRecovery() override;
+ virtual ITcpCongestionControl *createCongestionControl() override;
public:
- /** Ctor */
- TcpReno();
-
- /** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
-
- /** Redefine what should happen when dupAck was received, to add congestion window management */
- virtual void receivedDuplicateAck() override;
+ /** TcpReno selects RFC 6675 SACK recovery when the connection negotiated SACK. */
+ virtual bool supportsSackRecovery() const override { return true; }
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.cc b/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.cc
index 609766b1fa0..8da3bd420a3 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.cc
@@ -9,10 +9,17 @@
#include // min,max
+#include "inet/transportlayer/tcp_common/TcpHeader.h" // seqLE, seqLess
+
namespace inet {
namespace tcp {
+bool TcpSegmentTransmitInfoList::isInRange(uint32_t beg) const
+{
+ return regions.empty() || (seqLE(regions.front().beg, beg) && seqLE(beg, regions.back().end));
+}
+
void TcpSegmentTransmitInfoList::set(uint32_t beg, uint32_t end, simtime_t sentTime)
{
ASSERT(seqLess(beg, end));
diff --git a/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.h b/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.h
index ea973c33564..36fb77c274d 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpSegmentTransmitInfoList.h
@@ -8,7 +8,9 @@
#ifndef __INET_TCPSEGMENTTRANSMITINFOLIST_H
#define __INET_TCPSEGMENTTRANSMITINFOLIST_H
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
+#include
+
+#include "inet/common/INETDefs.h"
namespace inet {
@@ -39,6 +41,12 @@ class INET_API TcpSegmentTransmitInfoList
TcpSegmentTransmitInfoItems regions; // region[i].end == region[i+1].beg
public:
+ /**
+ * Whether beg lies within the contiguous range this list currently covers
+ * (or the list is empty), i.e. whether set() may be called with it.
+ */
+ bool isInRange(uint32_t beg) const;
+
void set(uint32_t beg, uint32_t end, simtime_t sentTime); // [beg,end)
/// returns pointer to Item, or nullptr if not found
diff --git a/src/inet/transportlayer/tcp/flavours/TcpTahoe.cc b/src/inet/transportlayer/tcp/flavours/TcpTahoe.cc
index c01b8c91041..9ebb2a750e0 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpTahoe.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpTahoe.cc
@@ -7,128 +7,81 @@
#include "inet/transportlayer/tcp/flavours/TcpTahoe.h"
-#include // min,max
-
-#include "inet/transportlayer/tcp/Tcp.h"
-
namespace inet {
namespace tcp {
Register_Class(TcpTahoe);
-TcpTahoe::TcpTahoe() : TcpTahoeRenoFamily(),
- state((TcpTahoeStateVariables *&)TcpAlgorithm::state)
+TcpTahoe::TcpTahoe() : TcpAlgorithmBase(),
+ state((TcpClassicAlgorithmBaseStateVariables *&)TcpAlgorithm::state)
{
}
-void TcpTahoe::recalculateSlowStartThreshold()
+void TcpTahoe::initialize()
{
- // set ssthresh to flight size / 2, but at least 2 MSS
- // (the formula below practically amounts to ssthresh = cwnd / 2 most of the time)
- uint32_t flight_size = std::min(state->snd_cwnd, state->snd_wnd); // FIXME - Does this formula computes the amount of outstanding data?
-// uint32_t flight_size = state->snd_max - state->snd_una;
- state->ssthresh = std::max(flight_size / 2, 2 * state->snd_mss);
-
- conn->emit(ssthreshSignal, state->ssthresh);
+ TcpAlgorithmBase::initialize();
+ state->ssthresh = conn->getTcpMain()->par("initialSsthresh");
}
void TcpTahoe::processRexmitTimer(TcpEventCode& event)
{
- TcpTahoeRenoFamily::processRexmitTimer(event);
+ TcpAlgorithmBase::processRexmitTimer(event);
if (event == TCP_E_ABORT)
return;
- // begin Slow Start (RFC 2581)
- recalculateSlowStartThreshold();
- state->snd_cwnd = state->snd_mss;
-
- conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_INFO << "Begin Slow Start: resetting cwnd to " << state->snd_cwnd
- << ", ssthresh=" << state->ssthresh << "\n";
-
- state->afterRto = true;
-
- // Tahoe retransmits only one segment at the front of the queue
- conn->retransmitOneSegment(true);
+ resetToSlowStart();
}
-void TcpTahoe::receivedDataAck(uint32_t firstSeqAcked)
+void TcpTahoe::receivedAckForUnackedData(uint32_t firstSeqAcked)
{
- TcpTahoeRenoFamily::receivedDataAck(firstSeqAcked);
-
- //
- // Perform slow start and congestion avoidance.
- //
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
if (state->snd_cwnd < state->ssthresh) {
- EV_DETAIL << "cwnd <= ssthresh: Slow Start: increasing cwnd by SMSS bytes to ";
-
- // perform Slow Start. RFC 2581: "During slow start, a TCP increments cwnd
- // by at most SMSS bytes for each ACK received that acknowledges new data."
state->snd_cwnd += state->snd_mss;
-
- // Note: we could increase cwnd based on the number of bytes being
- // acknowledged by each arriving ACK, rather than by the number of ACKs
- // that arrive. This is called "Appropriate Byte Counting" (ABC) and is
- // described in RFC 3465 (experimental).
- //
-// int bytesAcked = state->snd_una - firstSeqAcked;
-// state->snd_cwnd += bytesAcked;
-
conn->emit(cwndSignal, state->snd_cwnd);
-
- EV_DETAIL << "cwnd=" << state->snd_cwnd << "\n";
+ EV_INFO << "Incrementing cwnd in slow start" << EV_FIELD(cwnd, state->snd_cwnd) << EV_ENDL;
}
else {
- // perform Congestion Avoidance (RFC 2581)
- int incr = state->snd_mss * state->snd_mss / state->snd_cwnd;
-
- if (incr == 0)
- incr = 1;
-
- state->snd_cwnd += incr;
-
+ // congestion avoidance
+ state->snd_cwnd += std::max(1u, state->snd_mss * state->snd_mss / state->snd_cwnd);
conn->emit(cwndSignal, state->snd_cwnd);
-
- //
- // Note: some implementations use extra additive constant mss / 8 here
- // which is known to be incorrect (RFC 2581 p5)
- //
- // Note 2: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
- // would require maintaining a bytes_acked variable here which we don't do
- //
-
- EV_DETAIL << "cwnd>ssthresh: Congestion Avoidance: increasing cwnd linearly, to " << state->snd_cwnd << "\n";
+ EV_INFO << "Incrementing cwnd in congestion avoidance" << EV_FIELD(cwnd, state->snd_cwnd) << EV_ENDL;
+ }
+ if (state->dupacks != 0) {
+ state->dupacks = 0;
+ conn->emit(dupAcksSignal, state->dupacks);
}
-
- // ack and/or cwnd increase may have freed up some room in the window, try sending
sendData(false);
}
-void TcpTahoe::receivedDuplicateAck()
+void TcpTahoe::receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength)
{
- TcpTahoeRenoFamily::receivedDuplicateAck();
-
- if (state->dupacks == state->dupthresh) {
- EV_DETAIL << "Tahoe on dupAcks == DUPTHRESH(=" << state->dupthresh << ": perform Fast Retransmit, and enter Slow Start:\n";
-
- // enter Slow Start
- recalculateSlowStartThreshold();
- state->snd_cwnd = state->snd_mss;
+ bool isDupack = state->snd_una == tcpHeader->getAckNo() && payloadLength == 0 && state->snd_una != state->snd_max;
+ if (isDupack) {
+ state->dupacks++;
+ conn->emit(dupAcksSignal, state->dupacks);
+
+ if (state->dupacks == state->dupthresh)
+ resetToSlowStart();
+ else
+ sendData(false);
+ }
+ else
+ sendData(false);
+}
- conn->emit(cwndSignal, state->snd_cwnd);
+void TcpTahoe::resetToSlowStart()
+{
+ state->ssthresh = std::max(state->snd_cwnd / 2, 2 * state->snd_mss);
+ conn->emit(ssthreshSignal, state->ssthresh);
- EV_DETAIL << "Set cwnd=" << state->snd_cwnd << ", ssthresh=" << state->ssthresh << "\n";
+ state->snd_cwnd = state->snd_mss;
+ conn->emit(cwndSignal, state->snd_cwnd);
- // Fast Retransmission: retransmit missing segment without waiting
- // for the REXMIT timer to expire
- conn->retransmitOneSegment(false);
+ EV_INFO << "Beginning slow start" << EV_FIELD(ssthresh, state->ssthresh) << EV_FIELD(cwnd, state->snd_cwnd) << EV_ENDL;
- // Do not restart REXMIT timer.
- // Note: Restart of REXMIT timer on retransmission is not part of RFC 2581, however optional in RFC 3517 if sent during recovery.
- // Resetting the REXMIT timer is discussed in RFC 2582/3782 (NewReno) and RFC 2988.
- }
+ state->afterRto = true;
+ conn->retransmitOneSegment(true);
}
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpTahoe.h b/src/inet/transportlayer/tcp/flavours/TcpTahoe.h
index b3089aab2e8..399462bfdcf 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpTahoe.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpTahoe.h
@@ -8,46 +8,37 @@
#ifndef __INET_TCPTAHOE_H
#define __INET_TCPTAHOE_H
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h"
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
+#include "inet/transportlayer/tcp/flavours/TcpClassicAlgorithmBaseState_m.h"
namespace inet {
namespace tcp {
/**
- * State variables for TcpTahoe.
+ * This class serves educational purposes to demonstrate a simple congestion
+ * control algorithm. It implements slow start, congestion avoidance, and fast
+ * retransmit algorithms.
*/
-typedef TcpTahoeRenoFamilyStateVariables TcpTahoeStateVariables;
-
-/**
- * Implements Tahoe.
- */
-class INET_API TcpTahoe : public TcpTahoeRenoFamily
+class INET_API TcpTahoe : public TcpAlgorithmBase
{
protected:
- TcpTahoeStateVariables *& state; // alias to TCLAlgorithm's 'state'
+ TcpClassicAlgorithmBaseStateVariables *& state;
protected:
- /** Create and return a TcpTahoeStateVariables object. */
- virtual TcpStateVariables *createStateVariables() override
- {
- return new TcpTahoeStateVariables();
- }
+ virtual TcpStateVariables *createStateVariables() override { return new TcpClassicAlgorithmBaseStateVariables(); }
- /** Utility function to recalculate ssthresh */
- virtual void recalculateSlowStartThreshold();
+ virtual void initialize() override;
- /** Redefine what should happen on retransmission */
virtual void processRexmitTimer(TcpEventCode& event) override;
- public:
- /** Ctor */
- TcpTahoe();
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
+
+ virtual void receivedAckForAlreadyAckedData(const TcpHeader *tcpHeader, uint32_t payloadLength) override;
- /** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void resetToSlowStart();
- /** Redefine what should happen when dupAck was received, to add congestion window management */
- virtual void receivedDuplicateAck() override;
+ public:
+ TcpTahoe();
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.cc b/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.cc
deleted file mode 100644
index a78b009ec04..00000000000
--- a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-// Copyright (C) 2004 OpenSim Ltd.
-//
-// SPDX-License-Identifier: LGPL-3.0-or-later
-//
-
-
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h"
-
-#include "inet/transportlayer/tcp/Tcp.h"
-
-namespace inet {
-namespace tcp {
-
-void TcpTahoeRenoFamilyStateVariables::setSendQueueLimit(uint32_t newLimit)
-{
- // The initial value of ssthresh SHOULD be set arbitrarily high (e.g.,
- // to the size of the largest possible advertised window) -> defined by sendQueueLimit
- sendQueueLimit = newLimit;
- ssthresh = sendQueueLimit;
-}
-
-std::string TcpTahoeRenoFamilyStateVariables::str() const
-{
- std::stringstream out;
- out << TcpBaseAlgStateVariables::str();
- out << " ssthresh=" << ssthresh;
- return out.str();
-}
-
-std::string TcpTahoeRenoFamilyStateVariables::detailedInfo() const
-{
- std::stringstream out;
- out << TcpBaseAlgStateVariables::detailedInfo();
- out << "ssthresh=" << ssthresh << "\n";
- return out.str();
-}
-
-// ---
-
-TcpTahoeRenoFamily::TcpTahoeRenoFamily() : TcpBaseAlg(),
- state((TcpTahoeRenoFamilyStateVariables *&)TcpAlgorithm::state)
-{
-}
-
-void TcpTahoeRenoFamily::initialize()
-{
- TcpBaseAlg::initialize();
- state->ssthresh = conn->getTcpMain()->par("initialSsthresh");
-}
-
-} // namespace tcp
-} // namespace inet
-
diff --git a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h b/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h
deleted file mode 100644
index 23de57d0d90..00000000000
--- a/src/inet/transportlayer/tcp/flavours/TcpTahoeRenoFamily.h
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Copyright (C) 2004 OpenSim Ltd.
-//
-// SPDX-License-Identifier: LGPL-3.0-or-later
-//
-
-
-#ifndef __INET_TCPTAHOERENOFAMILY_H
-#define __INET_TCPTAHOERENOFAMILY_H
-
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
-#include "inet/transportlayer/tcp/flavours/TcpTahoeRenoFamilyState_m.h"
-
-namespace inet {
-namespace tcp {
-
-/**
- * Provides utility functions to implement TcpTahoe, TcpReno and TcpNewReno.
- * (TcpVegas should inherit from TcpBaseAlg instead of this one.)
- */
-class INET_API TcpTahoeRenoFamily : public TcpBaseAlg
-{
- protected:
- TcpTahoeRenoFamilyStateVariables *& state; // alias to TcpAlgorithm's 'state'
-
- public:
- /** Ctor */
- TcpTahoeRenoFamily();
-
- virtual void initialize() override;
-};
-
-} // namespace tcp
-} // namespace inet
-
-#endif
-
diff --git a/src/inet/transportlayer/tcp/flavours/TcpVegas.cc b/src/inet/transportlayer/tcp/flavours/TcpVegas.cc
index 4c8cc791412..58c3cfe9739 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpVegas.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpVegas.cc
@@ -19,7 +19,7 @@ Register_Class(TcpVegas);
std::string TcpVegasStateVariables::str() const
{
std::stringstream out;
- out << TcpBaseAlgStateVariables::str();
+ out << TcpAlgorithmBaseStateVariables::str();
out << " ssthresh=" << ssthresh;
return out.str();
}
@@ -27,29 +27,30 @@ std::string TcpVegasStateVariables::str() const
std::string TcpVegasStateVariables::detailedInfo() const
{
std::stringstream out;
- out << TcpBaseAlgStateVariables::detailedInfo();
+ out << TcpAlgorithmBaseStateVariables::detailedInfo();
out << "ssthresh = " << ssthresh << "\n";
out << "baseRTT = " << v_baseRTT << "\n";
return out.str();
}
TcpVegas::TcpVegas()
- : TcpBaseAlg(), state((TcpVegasStateVariables *&)TcpAlgorithm::state)
+ : TcpAlgorithmBase(), state((TcpVegasStateVariables *&)TcpAlgorithm::state)
{
}
// Same as TcpReno
void TcpVegas::recalculateSlowStartThreshold()
{
- // RFC 2581, page 4:
+ // RFC 5681, page 7:
// "When a TCP sender detects segment loss using the retransmission
- // timer, the value of ssthresh MUST be set to no more than the value
- // given in equation 3:
+ // timer and the given segment has not yet been resent by way of the
+ // retransmission timer, the value of ssthresh MUST be set to no more
+ // than the value given in equation 4:
//
- // ssthresh = max (FlightSize / 2, 2*SMSS) (3)
+ // ssthresh = max (FlightSize / 2, 2*SMSS) (4)
//
- // As discussed above, FlightSize is the amount of outstanding data in
- // the network."
+ // where, as discussed above, FlightSize is the amount of outstanding
+ // data in the network."
// set ssthresh to flight size/2, but at least 2 SMSS
// (the formula below practically amounts to ssthresh=cwnd/2 most of the time)
@@ -62,7 +63,7 @@ void TcpVegas::recalculateSlowStartThreshold()
// Process rexmit timer
void TcpVegas::processRexmitTimer(TcpEventCode& event)
{
- TcpBaseAlg::processRexmitTimer(event);
+ TcpAlgorithmBase::processRexmitTimer(event);
if (event == TCP_E_ABORT)
return;
@@ -82,11 +83,11 @@ void TcpVegas::processRexmitTimer(TcpEventCode& event)
conn->retransmitOneSegment(true); // retransmit one segment from snd_una
}
-void TcpVegas::receivedDataAck(uint32_t firstSeqAcked)
+void TcpVegas::receivedAckForUnackedData(uint32_t firstSeqAcked)
{
- TcpBaseAlg::receivedDataAck(firstSeqAcked);
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
- const TcpSegmentTransmitInfoList::Item *found = state->regions.get(firstSeqAcked);
+ const TcpSegmentTransmitInfoList::Item *found = state->sentInfo.get(firstSeqAcked);
if (found) {
simtime_t currentTime = simTime();
simtime_t tSent = found->getFirstSentTime();
@@ -235,7 +236,7 @@ void TcpVegas::receivedDataAck(uint32_t firstSeqAcked)
// check 1st and 2nd ack after a rtx
if (state->v_worried > 0) {
state->v_worried -= state->snd_mss;
- const TcpSegmentTransmitInfoList::Item *unaFound = state->regions.get(state->snd_una);
+ const TcpSegmentTransmitInfoList::Item *unaFound = state->sentInfo.get(state->snd_una);
// bool expired = unaFound && ((currentTime - unaFound->getLastSentTime()) >= state->v_rtt_timeout);
bool expired = unaFound && ((currentTime - unaFound->getFirstSentTime()) >= state->v_rtt_timeout);
@@ -251,7 +252,7 @@ void TcpVegas::receivedDataAck(uint32_t firstSeqAcked)
}
} // Closes if v_sendtime != nullptr
- state->regions.clearTo(state->snd_una);
+ state->sentInfo.clearTo(state->snd_una);
// Try to send more data
sendData(false);
@@ -259,17 +260,17 @@ void TcpVegas::receivedDataAck(uint32_t firstSeqAcked)
void TcpVegas::receivedDuplicateAck()
{
- TcpBaseAlg::receivedDuplicateAck();
+ TcpAlgorithmBase::receivedDuplicateAck();
simtime_t currentTime = simTime();
simtime_t tSent = 0;
int num_transmits = 0;
- const TcpSegmentTransmitInfoList::Item *found = state->regions.get(state->snd_una);
+ const TcpSegmentTransmitInfoList::Item *found = state->sentInfo.get(state->snd_una);
if (found) {
tSent = found->getFirstSentTime();
num_transmits = found->getTransmitCount();
}
- state->regions.clearTo(state->snd_una);
+ state->sentInfo.clearTo(state->snd_una);
// check Vegas timeout
bool expired = found && ((currentTime - tSent) >= state->v_rtt_timeout);
@@ -321,27 +322,6 @@ void TcpVegas::receivedDuplicateAck()
sendData(false);
}
-void TcpVegas::dataSent(uint32_t fromseq)
-{
- TcpBaseAlg::dataSent(fromseq);
-
- // save time when packet is sent
- // fromseq is the seq number of the 1st sent byte
- // we need this value, based on iss=0 (to store it the right way on the vector),
- // but iss is not a constant value (ej: iss=0), so it needs to be detemined each time
- // (this is why it is used: fromseq-state->iss)
-
- state->regions.clearTo(state->snd_una);
- state->regions.set(fromseq, state->snd_max, simTime());
-}
-
-void TcpVegas::segmentRetransmitted(uint32_t fromseq, uint32_t toseq)
-{
- TcpBaseAlg::segmentRetransmitted(fromseq, toseq);
-
- state->regions.set(fromseq, toseq, simTime());
-}
-
} // namespace tcp
} // namespace inet
diff --git a/src/inet/transportlayer/tcp/flavours/TcpVegas.h b/src/inet/transportlayer/tcp/flavours/TcpVegas.h
index 859b13f1385..79a8194c69f 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpVegas.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpVegas.h
@@ -7,13 +7,13 @@
#ifndef __INET_TCPVEGAS_H
#define __INET_TCPVEGAS_H
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
#include "inet/transportlayer/tcp/flavours/TcpVegasState_m.h"
namespace inet {
namespace tcp {
-class INET_API TcpVegas : public TcpBaseAlg
+class INET_API TcpVegas : public TcpAlgorithmBase
{
protected:
TcpVegasStateVariables *& state; // alias to TcpAlgorithm's 'state'
@@ -35,15 +35,10 @@ class INET_API TcpVegas : public TcpBaseAlg
TcpVegas();
/** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
/** Redefine what should happen when dupAck was received, to add congestion window management */
virtual void receivedDuplicateAck() override;
-
- /** Called after we send data */
- virtual void dataSent(uint32_t fromseq) override;
-
- virtual void segmentRetransmitted(uint32_t fromseq, uint32_t toseq) override;
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpVegasState.msg b/src/inet/transportlayer/tcp/flavours/TcpVegasState.msg
index 29add31c852..55a9abf4917 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpVegasState.msg
+++ b/src/inet/transportlayer/tcp/flavours/TcpVegasState.msg
@@ -6,15 +6,14 @@
//
import inet.common.INETDefs;
-import inet.transportlayer.tcp.flavours.TcpBaseAlgState;
-import inet.transportlayer.tcp.flavours.TcpSegmentTransmitInfoList;
+import inet.transportlayer.tcp.flavours.TcpAlgorithmBaseState;
namespace inet::tcp;
///
/// State variables for TcpVegas.
///
-struct TcpVegasStateVariables extends TcpBaseAlgStateVariables
+struct TcpVegasStateVariables extends TcpAlgorithmBaseStateVariables
{
@descriptor(readonly);
@@ -24,15 +23,13 @@ struct TcpVegasStateVariables extends TcpBaseAlgStateVariables
simtime_t v_baseRTT = SIMTIME_MAX;
simtime_t v_sumRTT = SIMTIME_ZERO; // sum of rtt's measured within one RTT
int v_cntRTT = 0; // # of rtt's measured within one RTT
- uint32_t v_begseq = 0; // register next pkt to be sent,for rtt calculation in receivedDataAck
+ uint32_t v_begseq = 0; // register next pkt to be sent,for rtt calculation in receivedAckForUnackedData
simtime_t v_begtime = 0; // register time for rtt calculation
simtime_t v_rtt_timeout = 1000.0; // vegas fine-grained timeout
simtime_t v_sa; // average for vegas fine-grained timeout
simtime_t v_sd; // deviation for vegas fine-grained timeout
- TcpSegmentTransmitInfoList regions;
-
uint32_t ssthresh = 65536; ///< slow start threshold
bool v_inc_flag = true; // for slow start: "exponential growth only every other RTT"
diff --git a/src/inet/transportlayer/tcp/flavours/TcpWestwood.cc b/src/inet/transportlayer/tcp/flavours/TcpWestwood.cc
index 9517212f8ff..7442ca9a3cc 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpWestwood.cc
+++ b/src/inet/transportlayer/tcp/flavours/TcpWestwood.cc
@@ -18,7 +18,7 @@ Register_Class(TcpWestwood);
std::string TcpWestwoodStateVariables::str() const
{
std::stringstream out;
- out << TcpBaseAlgStateVariables::str();
+ out << TcpAlgorithmBaseStateVariables::str();
out << " ssthresh=" << ssthresh;
return out.str();
}
@@ -26,14 +26,14 @@ std::string TcpWestwoodStateVariables::str() const
std::string TcpWestwoodStateVariables::detailedInfo() const
{
std::stringstream out;
- out << TcpBaseAlgStateVariables::detailedInfo();
+ out << TcpAlgorithmBaseStateVariables::detailedInfo();
out << "ssthresh = " << ssthresh << "\n";
out << "w_RTTmin = " << w_RTTmin << "\n";
return out.str();
}
TcpWestwood::TcpWestwood()
- : TcpBaseAlg(), state((TcpWestwoodStateVariables *&)TcpAlgorithm::state)
+ : TcpAlgorithmBase(), state((TcpWestwoodStateVariables *&)TcpAlgorithm::state)
{
}
@@ -65,7 +65,7 @@ void TcpWestwood::recalculateBWE(uint32_t cumul_ack)
void TcpWestwood::processRexmitTimer(TcpEventCode& event)
{
- TcpBaseAlg::processRexmitTimer(event);
+ TcpAlgorithmBase::processRexmitTimer(event);
if (event == TCP_E_ABORT)
return;
@@ -97,12 +97,14 @@ void TcpWestwood::processRexmitTimer(TcpEventCode& event)
conn->retransmitOneSegment(true);
}
-void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
+void TcpWestwood::receivedAckForUnackedData(uint32_t firstSeqAcked)
{
- TcpBaseAlg::receivedDataAck(firstSeqAcked);
+ uint32_t old_dupacks = state->dupacks;
- state->regions.clearTo(state->snd_una);
- const TcpSegmentTransmitInfoList::Item *found = state->regions.get(firstSeqAcked);
+ TcpAlgorithmBase::receivedAckForUnackedData(firstSeqAcked);
+
+ state->sentInfo.clearTo(state->snd_una);
+ const TcpSegmentTransmitInfoList::Item *found = state->sentInfo.get(firstSeqAcked);
if (found != nullptr) {
simtime_t currentTime = simTime();
@@ -115,10 +117,10 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
// cumul_ack: cumulative ack's that acks 2 or more pkts count 1,
// because DUPACKs count them
uint32_t cumul_ack = state->snd_una - firstSeqAcked; // acked bytes
- if ((state->dupacks * state->snd_mss) >= cumul_ack)
+ if ((old_dupacks * state->snd_mss) >= cumul_ack)
cumul_ack = state->snd_mss; // cumul_ack = 1:
else
- cumul_ack -= (state->dupacks * state->snd_mss);
+ cumul_ack -= (old_dupacks * state->snd_mss);
// security check: if previous steps are right cumul_ack shoudl be > 2:
if (cumul_ack > (2 * state->snd_mss))
@@ -129,7 +131,7 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
// Same behavior of Reno during fast recovery, slow start and cong. avoidance
- if (state->dupacks >= state->dupthresh) {
+ if (old_dupacks >= state->dupthresh) {
//
// Perform Fast Recovery: set cwnd to ssthresh (deflating the window).
//
@@ -145,7 +147,7 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
if (state->snd_cwnd < state->ssthresh) {
EV_DETAIL << "cwnd <= ssthresh: Slow Start: increasing cwnd by one SMSS bytes to ";
- // perform Slow Start. RFC 2581: "During slow start, a TCP increments cwnd
+ // perform Slow Start. RFC 5681: "During slow start, a TCP increments cwnd
// by at most SMSS bytes for each ACK received that acknowledges new data."
state->snd_cwnd += state->snd_mss;
@@ -165,7 +167,7 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
EV_DETAIL << "cwnd=" << state->snd_cwnd << "\n";
}
else {
- // perform Congestion Avoidance (RFC 2581)
+ // perform Congestion Avoidance (RFC 5681)
uint32_t incr = state->snd_mss * state->snd_mss / state->snd_cwnd;
if (incr == 0)
@@ -176,10 +178,7 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
conn->emit(cwndSignal, state->snd_cwnd);
//
- // Note: some implementations use extra additive constant mss / 8 here
- // which is known to be incorrect (RFC 2581 p5)
- //
- // Note 2: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
+ // Note: RFC 3465 (experimental) "Appropriate Byte Counting" (ABC)
// would require maintaining a bytes_acked variable here which we don't do
//
@@ -192,7 +191,7 @@ void TcpWestwood::receivedDataAck(uint32_t firstSeqAcked)
void TcpWestwood::receivedDuplicateAck()
{
- TcpBaseAlg::receivedDuplicateAck();
+ TcpAlgorithmBase::receivedDuplicateAck();
{
// BWE calculation: dupack counts 1
@@ -262,25 +261,6 @@ void TcpWestwood::receivedDuplicateAck()
}
}
-void TcpWestwood::dataSent(uint32_t fromseq)
-{
- TcpBaseAlg::dataSent(fromseq);
-
- // save time when packet is sent
- // fromseq is the seq number of the 1st sent byte
-
- simtime_t sendtime = simTime();
- state->regions.clearTo(state->snd_una);
- state->regions.set(fromseq, state->snd_max, sendtime);
-}
-
-void TcpWestwood::segmentRetransmitted(uint32_t fromseq, uint32_t toseq)
-{
- TcpBaseAlg::segmentRetransmitted(fromseq, toseq);
-
- state->regions.set(fromseq, toseq, simTime());
-}
-
} // namespace tcp
} // namespace inet
diff --git a/src/inet/transportlayer/tcp/flavours/TcpWestwood.h b/src/inet/transportlayer/tcp/flavours/TcpWestwood.h
index 93d17b4d45a..62a4ef310c3 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpWestwood.h
+++ b/src/inet/transportlayer/tcp/flavours/TcpWestwood.h
@@ -7,13 +7,13 @@
#ifndef __INET_TCPWESTWOOD_H
#define __INET_TCPWESTWOOD_H
-#include "inet/transportlayer/tcp/flavours/TcpBaseAlg.h"
+#include "inet/transportlayer/tcp/flavours/TcpAlgorithmBase.h"
#include "inet/transportlayer/tcp/flavours/TcpWestwoodState_m.h"
namespace inet {
namespace tcp {
-class INET_API TcpWestwood : public TcpBaseAlg
+class INET_API TcpWestwood : public TcpAlgorithmBase
{
protected:
TcpWestwoodStateVariables *& state; // alias to TCLAlgorithm's 'state'
@@ -38,15 +38,10 @@ class INET_API TcpWestwood : public TcpBaseAlg
TcpWestwood();
/** Redefine what should happen when data got acked, to add congestion window management */
- virtual void receivedDataAck(uint32_t firstSeqAcked) override;
+ virtual void receivedAckForUnackedData(uint32_t firstSeqAcked) override;
/** Redefine what should happen when dupAck was received, to add congestion window management */
virtual void receivedDuplicateAck() override;
-
- /** Called after we send data */
- virtual void dataSent(uint32_t fromseq) override;
-
- virtual void segmentRetransmitted(uint32_t fromseq, uint32_t toseq) override;
};
} // namespace tcp
diff --git a/src/inet/transportlayer/tcp/flavours/TcpWestwoodState.msg b/src/inet/transportlayer/tcp/flavours/TcpWestwoodState.msg
index c6a52003cd4..23041170d26 100644
--- a/src/inet/transportlayer/tcp/flavours/TcpWestwoodState.msg
+++ b/src/inet/transportlayer/tcp/flavours/TcpWestwoodState.msg
@@ -6,15 +6,14 @@
//
import inet.common.INETDefs;
-import inet.transportlayer.tcp.flavours.TcpBaseAlgState;
-import inet.transportlayer.tcp.flavours.TcpSegmentTransmitInfoList;
+import inet.transportlayer.tcp.flavours.TcpAlgorithmBaseState;
namespace inet::tcp;
///
/// State variables for TcpWestwood.
///
-struct TcpWestwoodStateVariables extends TcpBaseAlgStateVariables
+struct TcpWestwoodStateVariables extends TcpAlgorithmBaseStateVariables
{
@descriptor(readonly);
@@ -27,8 +26,6 @@ struct TcpWestwoodStateVariables extends TcpBaseAlgStateVariables
double w_bwe = 0.0;
double w_sample_bwe = 0.0;
-
- TcpSegmentTransmitInfoList regions;
};
cplusplus(TcpWestwoodStateVariables) {{
diff --git a/src/inet/transportlayer/tcp_common/TcpHeader.msg b/src/inet/transportlayer/tcp_common/TcpHeader.msg
index 2093ccce00c..a27ec0d1b9b 100644
--- a/src/inet/transportlayer/tcp_common/TcpHeader.msg
+++ b/src/inet/transportlayer/tcp_common/TcpHeader.msg
@@ -38,15 +38,15 @@ enum TcpConstants {
//
enum TcpOptionNumbers
{
- TCPOPTION_END_OF_OPTION_LIST = 0; // RFC 793, LENGTH: 1 Byte
- TCPOPTION_NO_OPERATION = 1; // RFC 793, LENGTH: 1 Byte
- TCPOPTION_MAXIMUM_SEGMENT_SIZE = 2; // RFC 793, LENGTH: 4 Bytes
- TCPOPTION_WINDOW_SCALE = 3; // RFC 1323, LENGTH: 3 Bytes
+ TCPOPTION_END_OF_OPTION_LIST = 0; // RFC 9293, LENGTH: 1 Byte
+ TCPOPTION_NO_OPERATION = 1; // RFC 9293, LENGTH: 1 Byte
+ TCPOPTION_MAXIMUM_SEGMENT_SIZE = 2; // RFC 9293, LENGTH: 4 Bytes
+ TCPOPTION_WINDOW_SCALE = 3; // RFC 7323, LENGTH: 3 Bytes
TCPOPTION_SACK_PERMITTED = 4; // RFC 2018, LENGTH: 2 Bytes
TCPOPTION_SACK = 5; // RFC 2018, LENGTH: N (max. N = 4) 8 * n + 2 Bytes => 32 + 2 + 2 * NOP = 36 Bytes; If TIMESTAMP option is used with SACK: max. n = 3 => 12 Bytes (for Timestamp) + 28 Bytes (for SACK) = 40 Bytes
// TCPOPTION_ECHO = 6; // (obsoleted by option 8) RFC 1072 & RFC 6247, LENGTH: 6 Bytes
// TCPOPTION_ECHO_REPLY = 7; // (obsoleted by option 8) RFC 1072 & RFC 6247, LENGTH: 6 Bytes
- TCPOPTION_TIMESTAMP = 8; // RFC 1323, LENGTH: 10 Bytes
+ TCPOPTION_TIMESTAMP = 8; // RFC 7323, LENGTH: 10 Bytes
// TCPOPTION_PARTIAL_ORDER_CONNECTION_PERMITTED = 9; // (obsolete) RFC 1693 & RFC 6247, LENGTH: 2 Bytes
// TCPOPTION_PARTIAL_ORDER_SERVICE_PROFILE = 10; // (obsolete) RFC 1693 & RFC 6247, LENGTH: 3 Bytes
// TCPOPTION_CC = 11; // (obsolete) RFC 1644 & RFC 6247, LENGTH: -
@@ -68,9 +68,15 @@ enum TcpOptionNumbers
// TCPOPTION_QUICK_START_RESPONSE = 27; // RFC 4782, LENGTH: 8 Bytes
// TCPOPTION_USER_TIMEOUT_OPTION = 28; // RFC 5482, LENGTH: 4 Bytes
// TCPOPTION_AUTHENTICATION_OPTION = 29; // RFC 5925, LENGTH: -
-// TCPOPTION kinds 30-252 Unassigned
+// TCPOPTION kinds 30-33 Unassigned
+ TCPOPTION_TCP_FASTOPEN = 34; // RFC 7413, LENGTH: 2 + cookie length (0 or 4-16 Bytes)
+// TCPOPTION kinds 35-171 Unassigned
+ TCPOPTION_ACCECN0 = 172; // draft-ietf-tcpm-accurate-ecn, LENGTH: 11 (E0B,CEB,E1B, 24-bit fields)
+// TCPOPTION kind 173 Unassigned
+ TCPOPTION_ACCECN1 = 174; // draft-ietf-tcpm-accurate-ecn, LENGTH: 11 (E1B,CEB,E0B, 24-bit fields)
+// TCPOPTION kinds 175-252 Unassigned
// TCPOPTION_RFC3692_STYLE_EXPERIMENT_1 = 253; // RFC 4727, LENGTH: N
-// TCPOPTION_RFC3692_STYLE_EXPERIMENT_2 = 254; // RFC 4727, LENGTH: N
+ TCPOPTION_RFC3692_STYLE_EXPERIMENT_2 = 254; // RFC 4727, LENGTH: N; also carries the pre-standardization TCP Fast Open experimental option (RFC 7413 Appendix A), identified by the 0xF989 magic sub-type
};
//
@@ -152,6 +158,43 @@ class TcpOptionTimestamp extends TcpOption
uint32_t echoedTimestamp;
}
+class TcpOptionTcpFastOpen extends TcpOption
+{
+ kind = TCPOPTION_TCP_FASTOPEN;
+ length = 2; // 2 + getCookieArraySize(); 0-byte cookie = "request a cookie"
+ uint8_t cookie[];
+}
+
+// Pre-standardization TCP Fast Open experimental option (RFC 7413 Appendix A):
+// kind 254 (RFC 4727 experimental) followed by the 0xF989 magic sub-type, then
+// the cookie bytes. Receive-only in INET (see readHeaderOptions()) -- kind 34
+// (TcpOptionTcpFastOpen above) is the modern standard and the only form INET
+// ever emits.
+class TcpOptionTcpFastOpenExp extends TcpOption
+{
+ kind = TCPOPTION_RFC3692_STYLE_EXPERIMENT_2;
+ length = 4; // 4 + getCookieArraySize()
+ uint16_t expId = 0xF989;
+ uint8_t cookie[];
+}
+
+// AccECN TCP option (draft-ietf-tcpm-accurate-ecn): three 24-bit (wire-encoded) byte
+// counters, always all 3 present (INET's own simplified beaconing; Linux's space-saving
+// partial-field packing is deliberately not ported). The
+// option's wire *order* of these 3 fields is kind-dependent (172: E0B,CEB,E1B; 174:
+// E1B,CEB,E0B) -- the serializer, not this class, encodes that; the fields below are
+// named semantically, not by wire position. kind defaults to 172 but callers building
+// an outbound option set it explicitly (setKind()) since either kind 172 or 174 is a
+// valid choice per emission (the alternation is beaconing policy, not a wire constraint).
+class TcpOptionAccEcn extends TcpOption
+{
+ kind = TCPOPTION_ACCECN0;
+ length = 11; // 2 (kind+length) + 3 * 3 (24-bit fields)
+ uint32_t ect0Bytes; // E0B: cumulative ECT(0)-marked bytes received, wire offset +1
+ uint32_t ect1Bytes; // E1B: cumulative ECT(1)-marked bytes received, wire offset +1
+ uint32_t ceBytes; // CEB: cumulative CE-marked bytes received, wire offset +0
+}
+
class TcpOptionUnknown extends TcpOption
{
kind = static_cast(-1);
@@ -198,6 +241,7 @@ class TcpHeader extends TransportHeaderBase
bool cwrBit; // CWR: congestion window reduced bit (RFC 3168)
bool eceBit; // ECE: ECN-echo bit (RFC 3168)
+ bool aeBit; // AE: AccECN Echo (repurposed NS/RFC3540 bit); handshake codepoint bit during 3WHS, ACE-counter MSB afterward (draft-ietf-tcpm-accurate-ecn)
bool urgBit; // URG: urgent pointer field significant if set
bool ackBit; // ACK: ackNo significant if set
bool pshBit; // PSH: push function
@@ -241,7 +285,7 @@ void doParsimUnpacking(omnetpp::cCommBuffer *b, TcpOption * &t)
cplusplus(TcpHeader) {{
public:
/**
- * Returns RFC 793 specified SEG.LEN:
+ * Returns RFC 9293 specified SEG.LEN:
* SEG.LEN = the number of octets occupied by the data in the segment
* (counting SYN and FIN)
*
diff --git a/src/inet/transportlayer/tcp_common/TcpHeaderSerializer.cc b/src/inet/transportlayer/tcp_common/TcpHeaderSerializer.cc
index 11947da6fbc..f75fe3aaf0c 100644
--- a/src/inet/transportlayer/tcp_common/TcpHeaderSerializer.cc
+++ b/src/inet/transportlayer/tcp_common/TcpHeaderSerializer.cc
@@ -34,7 +34,7 @@ void TcpHeaderSerializer::serialize(MemoryOutputStream& stream, const Ptr