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

Commit 953da4c

Browse files
authored
Merge pull request #634 from holbrookt/fix-tcp-endpoint-crash
Fix Weave TCPEndPoint crash in some lwIP cases
2 parents 105d949 + 6540b23 commit 953da4c

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)