fix: fix TCP stack bugs
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 13
|
||||
#define MONTAUK_BUILD_NUMBER 21
|
||||
|
||||
+57
-9
@@ -21,8 +21,8 @@ using namespace Kt;
|
||||
namespace Net::Tcp {
|
||||
|
||||
// Receive buffer size per connection
|
||||
static constexpr uint16_t RECV_BUFFER_SIZE = 4096;
|
||||
static constexpr uint16_t WINDOW_SIZE = 4096;
|
||||
static constexpr uint16_t RECV_BUFFER_SIZE = 32768;
|
||||
static constexpr uint16_t WINDOW_SIZE = RECV_BUFFER_SIZE;
|
||||
static constexpr uint32_t MAX_CONNECTIONS = 16;
|
||||
static constexpr uint64_t RETRANSMIT_TIMEOUT_MS = 1000;
|
||||
static constexpr int MAX_RETRANSMITS = 5;
|
||||
@@ -120,7 +120,9 @@ namespace Net::Tcp {
|
||||
hdr->AckNum = Htonl(conn->RecvNext);
|
||||
hdr->DataOffset = (HEADER_SIZE / 4) << 4;
|
||||
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->UrgentPtr = 0;
|
||||
|
||||
@@ -287,17 +289,41 @@ namespace Net::Tcp {
|
||||
notify = true;
|
||||
}
|
||||
|
||||
if (payloadLen > 0 && seqNum == conn->RecvNext) {
|
||||
RecvBufferWrite(conn, payload, payloadLen);
|
||||
conn->RecvNext += payloadLen;
|
||||
uint16_t accepted = 0;
|
||||
bool inOrder = (seqNum == conn->RecvNext);
|
||||
|
||||
// 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);
|
||||
notify = true;
|
||||
}
|
||||
|
||||
if (flags & FLAG_FIN) {
|
||||
conn->RecvNext = seqNum + payloadLen + 1;
|
||||
// Only accept the FIN when this segment is in order AND we
|
||||
// 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;
|
||||
|
||||
// Send ACK for the FIN
|
||||
@@ -673,6 +699,9 @@ namespace Net::Tcp {
|
||||
conn->Lock.Acquire();
|
||||
|
||||
if (conn->RecvCount > 0) {
|
||||
bool wasNearFull =
|
||||
(uint16_t)(RECV_BUFFER_SIZE - conn->RecvCount) < 2048;
|
||||
|
||||
uint16_t toRead = conn->RecvCount;
|
||||
if (toRead > bufferSize) {
|
||||
toRead = bufferSize;
|
||||
@@ -683,9 +712,14 @@ namespace Net::Tcp {
|
||||
conn->RecvHead = (conn->RecvHead + 1) % RECV_BUFFER_SIZE;
|
||||
}
|
||||
conn->RecvCount -= toRead;
|
||||
bool windowReopened = wasNearFull &&
|
||||
conn->CurrentState == State::Established;
|
||||
|
||||
conn->Lock.Release();
|
||||
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
|
||||
if (windowReopened) {
|
||||
SendSegment(conn, FLAG_ACK, nullptr, 0);
|
||||
}
|
||||
return toRead;
|
||||
}
|
||||
|
||||
@@ -716,7 +750,13 @@ namespace Net::Tcp {
|
||||
conn->Lock.Acquire();
|
||||
|
||||
int result;
|
||||
bool windowReopened = false;
|
||||
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;
|
||||
if (toRead > bufferSize) {
|
||||
toRead = bufferSize;
|
||||
@@ -728,6 +768,8 @@ namespace Net::Tcp {
|
||||
}
|
||||
conn->RecvCount -= toRead;
|
||||
result = toRead;
|
||||
windowReopened = wasNearFull &&
|
||||
conn->CurrentState == State::Established;
|
||||
} else if (conn->CurrentState == State::CloseWait ||
|
||||
conn->CurrentState == State::Closed ||
|
||||
conn->CurrentState == State::TimeWait) {
|
||||
@@ -739,6 +781,12 @@ namespace Net::Tcp {
|
||||
conn->Lock.Release();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user