From 25f9d6e401ff0dcdaa1f98e316b70a8294783813 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 13 Jun 2026 17:06:54 +0200 Subject: [PATCH] fix: fix TCP stack bugs --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Net/Tcp.cpp | 66 ++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 4ecbc75..feb58dc 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 13 +#define MONTAUK_BUILD_NUMBER 21 diff --git a/kernel/src/Net/Tcp.cpp b/kernel/src/Net/Tcp.cpp index bcd57fb..a7af84e 100644 --- a/kernel/src/Net/Tcp.cpp +++ b/kernel/src/Net/Tcp.cpp @@ -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; }