From da2d86f36150468b994792d90430621157a6ab41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:19:48 -0300 Subject: [PATCH 1/2] feat: resend SYN segment if it's lost --- src/types/network-modules/tcp/tcpState.ts | 99 +++++++++++++++++------ 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/src/types/network-modules/tcp/tcpState.ts b/src/types/network-modules/tcp/tcpState.ts index cacecc76..4795a2db 100644 --- a/src/types/network-modules/tcp/tcpState.ts +++ b/src/types/network-modules/tcp/tcpState.ts @@ -178,17 +178,10 @@ export class TcpState { this.sendUnacknowledged = this.initialSendSeqNum; // Send a SYN - const flags = new Flags().withSyn(); - const segment = this.newSegment(this.initialSendSeqNum, 0).withFlags(flags); - if (!sendIpPacket(this.srcHost, this.dstHost, segment)) { - console.warn( - `Device ${this.srcHost.id} couldn't send SYN to device ${this.dstHost.id}.`, - ); + if (!this.sendSynSegment()) { return false; } - this.rttEstimator.startMeasurement(this.initialSendSeqNum); - // Move to SYN_SENT state this.state = TcpStateEnum.SYN_SENT; return await this.connectionQueue.pop(); @@ -678,6 +671,13 @@ export class TcpState { segment.withFlags(new Flags().withRst()); sendIpPacket(this.srcHost, this.dstHost, segment); break; + } else if (result === "SYN") { + // Retransmit SYN packet + console.debug("[" + this.srcHost.id + "] [TCP] Processing SYN timeout"); + retransmitPromise = this.retransmissionQueue.pop(); + this.sendSynSegment(); + this.showTimeoutIcon(); + continue; } else if ("segment" in result) { console.debug("[" + this.srcHost.id + "] [TCP] Processing segments"); receivedSegmentPromise = this.tcpQueue.pop(); @@ -715,14 +715,8 @@ export class TcpState { console.debug("[" + this.srcHost.id + "] [TCP] Processing timeout"); retransmitPromise = this.retransmissionQueue.pop(); // Retransmit the segment - this.srcHost.showDeviceIconFor( - "tcp_timeout", - "⏰", - "TCP Timeout", - 2000, - Layer.Transport, - ); this.resendPacket(result.seqNum, result.size); + this.showTimeoutIcon(); this.congestionControl.notifyTimeout(); continue; } @@ -782,7 +776,28 @@ export class TcpState { this.tcpQueue.close(); } + private sendSynSegment() { + const flags = new Flags().withSyn(); + const segment = this.newSegment(this.initialSendSeqNum, 0).withFlags(flags); + if (!sendIpPacket(this.srcHost, this.dstHost, segment)) { + console.warn( + `Device ${this.srcHost.id} couldn't send SYN to device ${this.dstHost.id}.`, + ); + return false; + } + + // Add the SYN segment to the retransmission queue + this.retransmissionQueue.pushSyn(); + + // Reset measurement + this.rttEstimator.restartMeasurement(this.initialSendSeqNum); + return true; + } + private resendPacket(seqNum: number, size: number) { + if (seqNum === this.initialSendSeqNum && size === 0) { + // This is the initial SYN segment + } const segment = this.newSegment(seqNum, this.recvNext).withFlags( new Flags().withAck(), ); @@ -813,6 +828,10 @@ export class TcpState { if (!item) { return; } + if (item === "SYN") { + console.error("SYN segment retransmitted with an established connection"); + return; + } // Resend packet this.resendPacket(item.seqNum, item.size); } @@ -825,9 +844,20 @@ export class TcpState { const bytesInFlight = this.sendNext - this.sendUnacknowledged; return (windowSize - bytesInFlight) % u32_MODULUS; } + + private showTimeoutIcon() { + this.srcHost.showDeviceIconFor( + "tcp_timeout", + "⏰", + "TCP Timeout", + 2000, + Layer.Transport, + ); + } } -interface RetransmissionQueueItem { +type RetransmissionQueueItem = DataSegment | "SYN"; +interface DataSegment { seqNum: number; size: number; } @@ -845,14 +875,26 @@ class RetransmissionQueue { this.rttEstimator = rttEstimator; } + pushSyn() { + this.itemQueue.unshift("SYN"); + this.startTimer(); + } + push(seqNum: number, size: number) { const item = { seqNum, size }; this.itemQueue.push(item); - this.itemQueue.sort((a, b) => a.seqNum - b.seqNum); + this.itemQueue.sort((a, b) => { + // SYN segments should always be at the front + if (a === "SYN") { + return -1; + } + if (b === "SYN") { + return 1; + } + return a.seqNum - b.seqNum; + }); - if (!this.timeoutTick) { - this.startTimer(); - } + this.startTimer(); } /** @@ -870,6 +912,10 @@ class RetransmissionQueue { ack(ackNum: number) { this.itemQueue = this.itemQueue.filter((item) => { + // We treat any valid ACK as a SYN ACK + if (item === "SYN") { + return false; + } return !( item.seqNum < ackNum || (item.seqNum + item.size) % u32_MODULUS <= ackNum @@ -884,9 +930,10 @@ class RetransmissionQueue { if (this.itemQueue.length === 0) { return; } - const firstSegmentItem = this.itemQueue[0]; - // Remove the segment from the queue - this.ack(firstSegmentItem.seqNum + 1); + const firstSegmentItem = this.itemQueue.shift(); + if (this.itemQueue.length === 0) { + this.stopTimer(); + } return firstSegmentItem; } @@ -1162,6 +1209,12 @@ class RTTEstimator { Ticker.shared.remove(this.measureTick, this); } + restartMeasurement(seqNum: number) { + // Restart the measurement for the segment + this.discardMeasurement(seqNum); + this.startMeasurement(seqNum); + } + private measureTick(ticker: Ticker) { // Update the current sample's RTT // NOTE: we do this to account for the simulation's speed From 1457060a999505e1473420b1679cfeab462fbc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:25:47 -0300 Subject: [PATCH 2/2] fix: SYN-ACK now stops retransmit of SYN --- src/types/network-modules/tcp/tcpState.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/types/network-modules/tcp/tcpState.ts b/src/types/network-modules/tcp/tcpState.ts index 4795a2db..8a42c47d 100644 --- a/src/types/network-modules/tcp/tcpState.ts +++ b/src/types/network-modules/tcp/tcpState.ts @@ -301,12 +301,11 @@ export class TcpState { this.initialRecvSeqNum = segment.sequenceNumber; if (flags.ack) { this.sendUnacknowledged = segment.acknowledgementNumber; - } - if (flags.ack) { // It's a valid SYN-ACK // Process the segment normally this.state = TcpStateEnum.ESTABLISHED; this.connectionQueue.push(true); + this.retransmissionQueue.ack(segment.acknowledgementNumber); if (this.handleSegmentData(segment) !== ProcessingResult.SUCCESS) { console.debug("Segment data processing failed"); return ProcessingResult.DISCARD;