fix: fix TCP stack bugs

This commit is contained in:
2026-06-13 17:06:54 +02:00
parent 44f86a8b19
commit 25f9d6e401
2 changed files with 58 additions and 10 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 13 #define MONTAUK_BUILD_NUMBER 21
+57 -9
View File
@@ -21,8 +21,8 @@ using namespace Kt;
namespace Net::Tcp { namespace Net::Tcp {
// Receive buffer size per connection // Receive buffer size per connection
static constexpr uint16_t RECV_BUFFER_SIZE = 4096; static constexpr uint16_t RECV_BUFFER_SIZE = 32768;
static constexpr uint16_t WINDOW_SIZE = 4096; static constexpr uint16_t WINDOW_SIZE = RECV_BUFFER_SIZE;
static constexpr uint32_t MAX_CONNECTIONS = 16; static constexpr uint32_t MAX_CONNECTIONS = 16;
static constexpr uint64_t RETRANSMIT_TIMEOUT_MS = 1000; static constexpr uint64_t RETRANSMIT_TIMEOUT_MS = 1000;
static constexpr int MAX_RETRANSMITS = 5; static constexpr int MAX_RETRANSMITS = 5;
@@ -120,7 +120,9 @@ namespace Net::Tcp {
hdr->AckNum = Htonl(conn->RecvNext); hdr->AckNum = Htonl(conn->RecvNext);
hdr->DataOffset = (HEADER_SIZE / 4) << 4; hdr->DataOffset = (HEADER_SIZE / 4) << 4;
hdr->Flags = flags; hdr->Flags = flags;
hdr->Window = Htons(WINDOW_SIZE); // Advertise the actual remaining buffer space so the peer throttles
// itself instead of overrunning the ring.
hdr->Window = Htons((uint16_t)(RECV_BUFFER_SIZE - conn->RecvCount));
hdr->Checksum = 0; hdr->Checksum = 0;
hdr->UrgentPtr = 0; hdr->UrgentPtr = 0;
@@ -287,17 +289,41 @@ namespace Net::Tcp {
notify = true; notify = true;
} }
if (payloadLen > 0 && seqNum == conn->RecvNext) { uint16_t accepted = 0;
RecvBufferWrite(conn, payload, payloadLen); bool inOrder = (seqNum == conn->RecvNext);
conn->RecvNext += payloadLen;
// Send ACK if (payloadLen > 0 && inOrder) {
// Accept only what fits in the ring and ACK exactly that.
// ACKing dropped bytes loses them permanently (the peer
// never retransmits ACKed data), which corrupts the
// stream — fatal for TLS. Unaccepted bytes are simply
// retransmitted from RecvNext later.
uint16_t space = RECV_BUFFER_SIZE - conn->RecvCount;
accepted = payloadLen < space ? payloadLen : space;
if (accepted > 0) {
RecvBufferWrite(conn, payload, accepted);
conn->RecvNext += accepted;
}
SendSegment(conn, FLAG_ACK, nullptr, 0);
notify = true;
} else if (payloadLen > 0) {
// Out-of-order data: there is a gap before this segment.
// Drop it but send a duplicate ACK for the byte we are
// still waiting on, so the peer fast-retransmits the gap.
// Real networks reorder mid-stream bursts; without this
// the peer only recovers after a (slow) retransmit timeout.
SendSegment(conn, FLAG_ACK, nullptr, 0); SendSegment(conn, FLAG_ACK, nullptr, 0);
notify = true; notify = true;
} }
if (flags & FLAG_FIN) { // Only accept the FIN when this segment is in order AND we
conn->RecvNext = seqNum + payloadLen + 1; // consumed all of its payload — otherwise the FIN sits beyond
// a hole in the stream. Acknowledging it here would advance
// RecvNext past data we never received, silently truncating
// the stream (fatal for TLS). Leaving the connection
// Established makes the peer retransmit the gap and the FIN.
if ((flags & FLAG_FIN) && inOrder && accepted == payloadLen) {
conn->RecvNext += 1; // FIN occupies one sequence number
conn->CurrentState = State::CloseWait; conn->CurrentState = State::CloseWait;
// Send ACK for the FIN // Send ACK for the FIN
@@ -673,6 +699,9 @@ namespace Net::Tcp {
conn->Lock.Acquire(); conn->Lock.Acquire();
if (conn->RecvCount > 0) { if (conn->RecvCount > 0) {
bool wasNearFull =
(uint16_t)(RECV_BUFFER_SIZE - conn->RecvCount) < 2048;
uint16_t toRead = conn->RecvCount; uint16_t toRead = conn->RecvCount;
if (toRead > bufferSize) { if (toRead > bufferSize) {
toRead = bufferSize; toRead = bufferSize;
@@ -683,9 +712,14 @@ namespace Net::Tcp {
conn->RecvHead = (conn->RecvHead + 1) % RECV_BUFFER_SIZE; conn->RecvHead = (conn->RecvHead + 1) % RECV_BUFFER_SIZE;
} }
conn->RecvCount -= toRead; conn->RecvCount -= toRead;
bool windowReopened = wasNearFull &&
conn->CurrentState == State::Established;
conn->Lock.Release(); conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory"); asm volatile("push %0; popfq" :: "r"(flags) : "memory");
if (windowReopened) {
SendSegment(conn, FLAG_ACK, nullptr, 0);
}
return toRead; return toRead;
} }
@@ -716,7 +750,13 @@ namespace Net::Tcp {
conn->Lock.Acquire(); conn->Lock.Acquire();
int result; int result;
bool windowReopened = false;
if (conn->RecvCount > 0) { if (conn->RecvCount > 0) {
// If the advertised window was (nearly) closed, the peer is
// stalled waiting for it to reopen — announce the drain below.
bool wasNearFull =
(uint16_t)(RECV_BUFFER_SIZE - conn->RecvCount) < 2048;
uint16_t toRead = conn->RecvCount; uint16_t toRead = conn->RecvCount;
if (toRead > bufferSize) { if (toRead > bufferSize) {
toRead = bufferSize; toRead = bufferSize;
@@ -728,6 +768,8 @@ namespace Net::Tcp {
} }
conn->RecvCount -= toRead; conn->RecvCount -= toRead;
result = toRead; result = toRead;
windowReopened = wasNearFull &&
conn->CurrentState == State::Established;
} else if (conn->CurrentState == State::CloseWait || } else if (conn->CurrentState == State::CloseWait ||
conn->CurrentState == State::Closed || conn->CurrentState == State::Closed ||
conn->CurrentState == State::TimeWait) { conn->CurrentState == State::TimeWait) {
@@ -739,6 +781,12 @@ namespace Net::Tcp {
conn->Lock.Release(); conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory"); asm volatile("push %0; popfq" :: "r"(flags) : "memory");
if (windowReopened) {
// Window-update ACK so the peer resumes without waiting for a
// zero-window probe.
SendSegment(conn, FLAG_ACK, nullptr, 0);
}
return result; return result;
} }