fix: fix TCP stack bugs
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 13
|
#define MONTAUK_BUILD_NUMBER 21
|
||||||
|
|||||||
+57
-9
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user