Skip to content
This repository was archived by the owner on Dec 20, 2023. It is now read-only.

Commit 6540b23

Browse files
committed
Fix Weave TCPEndPoint crash in some lwIP cases
* Weave, when using an lwIP networking interface, employs the raw lwIP callback-based scheme to handle TCP sends. In order to avoid multiple copies of packet buffers on small devices, the TCP sends are done in-place over the incoming packet buffers. Packet buffers must be retained in full until acknowledgement has been received by lwIP. * The existing logic had a corner case where if there was more data queued than the local TCP send window, the accounting of buffers and lengths to retain the un-acked buffers could get out of balance, leading to an eventual crash. This was root caused to an invalid assumption that the packet buffer chain did not need traversal for offset readjustment (which is true in most cases, only by coincidence). * This change removes separate accounting of the unsent data position and replaces it with simple counting of sent-but-not-acked data and a skip-loop at the start of DriveSending().
1 parent e2ba235 commit 6540b23

File tree

2 files changed

+133
-32
lines changed

2 files changed

+133
-32
lines changed

src/inet/TCPEndPoint.cpp

Lines changed: 124 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,6 @@ INET_ERROR TCPEndPoint::Send(PacketBuffer *data, bool push)
708708

709709
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
710710

711-
if (mUnsentQueue == NULL)
712-
{
713-
mUnsentQueue = data;
714-
mUnsentOffset = 0;
715-
}
716-
717711
#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
718712
if (!mUserTimeoutTimerRunning)
719713
{
@@ -1199,6 +1193,10 @@ void TCPEndPoint::Init(InetLayer *inetLayer)
11991193
#endif // WEAVE_SYSTEM_CONFIG_USE_SOCKETS
12001194

12011195
#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
1196+
1197+
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
1198+
mUnackedLength = 0;
1199+
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
12021200
}
12031201

12041202
INET_ERROR TCPEndPoint::DriveSending()
@@ -1219,44 +1217,60 @@ INET_ERROR TCPEndPoint::DriveSending()
12191217
uint16_t sendWindowSize = tcp_sndbuf(mTCP);
12201218

12211219
// If there's data to be sent and the send window is open...
1222-
bool canSend = (mUnsentQueue != NULL && sendWindowSize > 0);
1220+
bool canSend = (RemainingToSend() > 0 && sendWindowSize > 0);
12231221
if (canSend)
12241222
{
1223+
// Find first packet buffer with remaining data to send by skipping
1224+
// all sent but un-acked data.
1225+
TCPEndPoint::BufferOffset startOfUnsent = FindStartOfUnsent();
1226+
const Weave::System::PacketBuffer* currentUnsentBuf = startOfUnsent.buffer;
1227+
uint16_t unsentOffset = startOfUnsent.offset;
1228+
12251229
// While there's data to be sent and a window to send it in...
12261230
do
12271231
{
1228-
uint16_t bufDataLen = mUnsentQueue->DataLength();
1232+
VerifyOrDie(currentUnsentBuf != NULL);
1233+
1234+
uint16_t bufDataLen = currentUnsentBuf->DataLength();
12291235

12301236
// Get a pointer to the start of unsent data within the first buffer on the unsent queue.
1231-
uint8_t *sendData = mUnsentQueue->Start() + mUnsentOffset;
1237+
const uint8_t *sendData = currentUnsentBuf->Start() + unsentOffset;
12321238

12331239
// Determine the amount of data to send from the current buffer.
1234-
uint16_t sendLen = bufDataLen - mUnsentOffset;
1240+
uint16_t sendLen = bufDataLen - unsentOffset;
12351241
if (sendLen > sendWindowSize)
12361242
sendLen = sendWindowSize;
12371243

1238-
// Adjust the unsent data offset by the length of data to be written. If the entire buffer
1239-
// has been sent advance to the next one.
1240-
mUnsentOffset += sendLen;
1241-
if (mUnsentOffset == bufDataLen)
1242-
{
1243-
mUnsentQueue = mUnsentQueue->Next();
1244-
mUnsentOffset = 0;
1245-
}
1246-
1247-
// Adjust the remaining window size.
1248-
sendWindowSize -= sendLen;
1249-
1250-
// Determine if there's more data to be sent after this buffer.
1251-
canSend = (mUnsentQueue != NULL && sendWindowSize > 0);
1252-
12531244
// Call LwIP to queue the data to be sent, telling it if there's more data to come.
1245+
// Data is queued in-place as a reference within the source packet buffer. It is
1246+
// critical that the underlying packet buffer not be freed until the data
1247+
// is acknowledged, otherwise retransmissions could use an invalid
1248+
// backing. Using TCP_WRITE_FLAG_COPY would eliminate this requirement, but overall
1249+
// requires many more memory allocations which may be problematic when very
1250+
// memory-constrained or when using pool-based allocations.
12541251
lwipErr = tcp_write(mTCP, sendData, sendLen, (canSend) ? TCP_WRITE_FLAG_MORE : 0);
12551252
if (lwipErr != ERR_OK)
12561253
{
12571254
err = Weave::System::MapErrorLwIP(lwipErr);
12581255
break;
12591256
}
1257+
// Start accounting for the data sent as yet-to-be-acked.
1258+
mUnackedLength += sendLen;
1259+
1260+
// Adjust the unsent data offset by the length of data that was written.
1261+
// If the entire buffer has been sent advance to the next one.
1262+
unsentOffset += sendLen;
1263+
if (unsentOffset == bufDataLen)
1264+
{
1265+
currentUnsentBuf = currentUnsentBuf->Next();
1266+
unsentOffset = 0;
1267+
}
1268+
1269+
// Adjust the remaining window size.
1270+
sendWindowSize -= sendLen;
1271+
1272+
// Determine if there's more data to be sent after this buffer.
1273+
canSend = (RemainingToSend() > 0 && sendWindowSize > 0);
12601274
} while (canSend);
12611275

12621276
// Call LwIP to send the queued data.
@@ -1274,7 +1288,7 @@ INET_ERROR TCPEndPoint::DriveSending()
12741288
if (err == INET_NO_ERROR)
12751289
{
12761290
// If in the SendShutdown state and the unsent queue is now empty, shutdown the PCB for sending.
1277-
if (State == kState_SendShutdown && mUnsentQueue == NULL)
1291+
if (State == kState_SendShutdown && (RemainingToSend() == 0))
12781292
{
12791293
lwipErr = tcp_shutdown(mTCP, 0, 1);
12801294
if (lwipErr != ERR_OK)
@@ -1571,6 +1585,9 @@ INET_ERROR TCPEndPoint::DoClose(INET_ERROR err, bool suppressCallback)
15711585
mSendQueue = NULL;
15721586
PacketBuffer::Free(mRcvQueue);
15731587
mRcvQueue = NULL;
1588+
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
1589+
mUnackedLength = 0;
1590+
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
15741591

15751592
// Call the appropriate app callback if allowed.
15761593
if (!suppressCallback)
@@ -1752,6 +1769,67 @@ void TCPEndPoint::RestartTCPUserTimeoutTimer()
17521769

17531770
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
17541771

1772+
uint32_t TCPEndPoint::RemainingToSend()
1773+
{
1774+
if (mSendQueue == NULL)
1775+
{
1776+
return 0;
1777+
}
1778+
else
1779+
{
1780+
// We can never have reported more unacked data than there is pending
1781+
// in the send queue! This would indicate a critical accounting bug.
1782+
VerifyOrDie(mUnackedLength <= mSendQueue->TotalLength());
1783+
1784+
return mSendQueue->TotalLength() - mUnackedLength;
1785+
}
1786+
}
1787+
1788+
TCPEndPoint::BufferOffset TCPEndPoint::FindStartOfUnsent()
1789+
{
1790+
// Find first packet buffer with remaining data to send by skipping
1791+
// all sent but un-acked data. This is necessary because of the Consume()
1792+
// call in HandleDataSent(), which potentially releases backing memory for
1793+
// fully-sent packet buffers, causing an invalidation of all possible
1794+
// offsets one might have cached. The TCP acnowledgements may come back
1795+
// with a variety of sizes depending on prior activity, and size of the
1796+
// send window. The only way to ensure we get the correct offsets into
1797+
// unsent data while retaining the buffers that have un-acked data is to
1798+
// traverse all sent-but-unacked data in the chain to reach the beginning
1799+
// of ready-to-send data.
1800+
Weave::System::PacketBuffer* currentUnsentBuf = mSendQueue;
1801+
uint16_t unsentOffset = 0;
1802+
uint32_t leftToSkip = mUnackedLength;
1803+
1804+
VerifyOrDie(leftToSkip < mSendQueue->TotalLength());
1805+
1806+
while (leftToSkip > 0)
1807+
{
1808+
VerifyOrDie(currentUnsentBuf != NULL);
1809+
uint16_t bufDataLen = currentUnsentBuf->DataLength();
1810+
if (leftToSkip >= bufDataLen)
1811+
{
1812+
// We have more to skip than current packet buffer size.
1813+
// Follow the chain to continue.
1814+
currentUnsentBuf = currentUnsentBuf->Next();
1815+
leftToSkip -= bufDataLen;
1816+
}
1817+
else
1818+
{
1819+
// Done skipping all data, currentUnsentBuf is first packet buffer
1820+
// containing unsent data.
1821+
unsentOffset = leftToSkip;
1822+
leftToSkip = 0;
1823+
}
1824+
}
1825+
1826+
TCPEndPoint::BufferOffset startOfUnsent;
1827+
startOfUnsent.buffer = currentUnsentBuf;
1828+
startOfUnsent.offset = unsentOffset;
1829+
1830+
return startOfUnsent;
1831+
}
1832+
17551833
INET_ERROR TCPEndPoint::GetPCB(IPAddressType addrType)
17561834
{
17571835
// IMMPORTANT: This method MUST be called with the LwIP stack LOCKED!
@@ -1843,16 +1921,32 @@ void TCPEndPoint::HandleDataSent(uint16_t lenSent)
18431921
{
18441922
if (IsConnected())
18451923
{
1924+
// Ensure we do not have internal inconsistency in the lwIP, which
1925+
// could cause invalid pointer accesses.
1926+
if (lenSent > mUnackedLength)
1927+
{
1928+
WeaveLogError(Inet, "Got more ACKed bytes (%d) than were pending (%d)", (int)lenSent, (int)mUnackedLength);
1929+
DoClose(INET_ERROR_UNEXPECTED_EVENT, false);
1930+
return;
1931+
}
1932+
else if (mSendQueue == NULL)
1933+
{
1934+
WeaveLogError(Inet, "Got ACK for %d bytes but data backing gone", (int)lenSent);
1935+
DoClose(INET_ERROR_UNEXPECTED_EVENT, false);
1936+
return;
1937+
}
1938+
18461939
// Consume data off the head of the send queue equal to the amount of data being acknowledged.
18471940
mSendQueue = mSendQueue->Consume(lenSent);
1941+
mUnackedLength -= lenSent;
18481942

18491943
#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
18501944
// Only change the UserTimeout timer if lenSent > 0,
18511945
// indicating progress being made in sending data
18521946
// across.
18531947
if (lenSent > 0)
18541948
{
1855-
if (mSendQueue == NULL)
1949+
if (RemainingToSend() == 0)
18561950
{
18571951
// If the output queue has been flushed then stop the timer.
18581952

@@ -1880,12 +1974,12 @@ void TCPEndPoint::HandleDataSent(uint16_t lenSent)
18801974
if (OnDataSent != NULL)
18811975
OnDataSent(this, lenSent);
18821976

1883-
// If unsent data exists, attempt to sent it now...
1884-
if (mUnsentQueue != NULL)
1977+
// If unsent data exists, attempt to send it now...
1978+
if (RemainingToSend() > 0)
18851979
DriveSending();
18861980

18871981
// If in the closing state and the send queue is now empty, attempt to transition to closed.
1888-
if (State == kState_Closing && mSendQueue == NULL)
1982+
if ((State == kState_Closing) && (RemainingToSend() == 0))
18891983
DoClose(INET_NO_ERROR, false);
18901984
}
18911985
}

src/inet/TCPEndPoint.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,9 +621,16 @@ class NL_DLL_EXPORT TCPEndPoint : public EndPointBasis
621621
void StopConnectTimer(void);
622622

623623
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
624-
Weave::System::PacketBuffer *mUnsentQueue;
625-
uint16_t mUnsentOffset;
624+
struct BufferOffset {
625+
const Weave::System::PacketBuffer *buffer;
626+
uint32_t offset;
627+
};
626628

629+
uint32_t mUnackedLength; // Amount sent but awaiting ACK. Used as a form of reference count
630+
// to hang-on to backing packet buffers until they are no longer needed.
631+
632+
uint32_t RemainingToSend();
633+
BufferOffset FindStartOfUnsent();
627634
INET_ERROR GetPCB(IPAddressType addrType);
628635
void HandleDataSent(uint16_t len);
629636
void HandleDataReceived(Weave::System::PacketBuffer *buf);

0 commit comments

Comments
 (0)