Skip to content

Commit ba367f1

Browse files
feat: resend SYN segment if it's lost (#270)
This PR adds retransmit logic for SYN segments. --------- Co-authored-by: Pedro Gallino <[email protected]>
1 parent db23f7e commit ba367f1

File tree

1 file changed

+77
-25
lines changed

1 file changed

+77
-25
lines changed

src/types/network-modules/tcp/tcpState.ts

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -178,17 +178,10 @@ export class TcpState {
178178
this.sendUnacknowledged = this.initialSendSeqNum;
179179

180180
// Send a SYN
181-
const flags = new Flags().withSyn();
182-
const segment = this.newSegment(this.initialSendSeqNum, 0).withFlags(flags);
183-
if (!sendIpPacket(this.srcHost, this.dstHost, segment)) {
184-
console.warn(
185-
`Device ${this.srcHost.id} couldn't send SYN to device ${this.dstHost.id}.`,
186-
);
181+
if (!this.sendSynSegment()) {
187182
return false;
188183
}
189184

190-
this.rttEstimator.startMeasurement(this.initialSendSeqNum);
191-
192185
// Move to SYN_SENT state
193186
this.state = TcpStateEnum.SYN_SENT;
194187
return await this.connectionQueue.pop();
@@ -308,12 +301,11 @@ export class TcpState {
308301
this.initialRecvSeqNum = segment.sequenceNumber;
309302
if (flags.ack) {
310303
this.sendUnacknowledged = segment.acknowledgementNumber;
311-
}
312-
if (flags.ack) {
313304
// It's a valid SYN-ACK
314305
// Process the segment normally
315306
this.state = TcpStateEnum.ESTABLISHED;
316307
this.connectionQueue.push(true);
308+
this.retransmissionQueue.ack(segment.acknowledgementNumber);
317309
if (this.handleSegmentData(segment) !== ProcessingResult.SUCCESS) {
318310
console.debug("Segment data processing failed");
319311
return ProcessingResult.DISCARD;
@@ -678,6 +670,13 @@ export class TcpState {
678670
segment.withFlags(new Flags().withRst());
679671
sendIpPacket(this.srcHost, this.dstHost, segment);
680672
break;
673+
} else if (result === "SYN") {
674+
// Retransmit SYN packet
675+
console.debug("[" + this.srcHost.id + "] [TCP] Processing SYN timeout");
676+
retransmitPromise = this.retransmissionQueue.pop();
677+
this.sendSynSegment();
678+
this.showTimeoutIcon();
679+
continue;
681680
} else if ("segment" in result) {
682681
console.debug("[" + this.srcHost.id + "] [TCP] Processing segments");
683682
receivedSegmentPromise = this.tcpQueue.pop();
@@ -715,14 +714,8 @@ export class TcpState {
715714
console.debug("[" + this.srcHost.id + "] [TCP] Processing timeout");
716715
retransmitPromise = this.retransmissionQueue.pop();
717716
// Retransmit the segment
718-
this.srcHost.showDeviceIconFor(
719-
"tcp_timeout",
720-
"⏰",
721-
"TCP Timeout",
722-
2000,
723-
Layer.Transport,
724-
);
725717
this.resendPacket(result.seqNum, result.size);
718+
this.showTimeoutIcon();
726719
this.congestionControl.notifyTimeout();
727720
continue;
728721
}
@@ -782,7 +775,28 @@ export class TcpState {
782775
this.tcpQueue.close();
783776
}
784777

778+
private sendSynSegment() {
779+
const flags = new Flags().withSyn();
780+
const segment = this.newSegment(this.initialSendSeqNum, 0).withFlags(flags);
781+
if (!sendIpPacket(this.srcHost, this.dstHost, segment)) {
782+
console.warn(
783+
`Device ${this.srcHost.id} couldn't send SYN to device ${this.dstHost.id}.`,
784+
);
785+
return false;
786+
}
787+
788+
// Add the SYN segment to the retransmission queue
789+
this.retransmissionQueue.pushSyn();
790+
791+
// Reset measurement
792+
this.rttEstimator.restartMeasurement(this.initialSendSeqNum);
793+
return true;
794+
}
795+
785796
private resendPacket(seqNum: number, size: number) {
797+
if (seqNum === this.initialSendSeqNum && size === 0) {
798+
// This is the initial SYN segment
799+
}
786800
const segment = this.newSegment(seqNum, this.recvNext).withFlags(
787801
new Flags().withAck(),
788802
);
@@ -813,6 +827,10 @@ export class TcpState {
813827
if (!item) {
814828
return;
815829
}
830+
if (item === "SYN") {
831+
console.error("SYN segment retransmitted with an established connection");
832+
return;
833+
}
816834
// Resend packet
817835
this.resendPacket(item.seqNum, item.size);
818836
}
@@ -825,9 +843,20 @@ export class TcpState {
825843
const bytesInFlight = this.sendNext - this.sendUnacknowledged;
826844
return (windowSize - bytesInFlight) % u32_MODULUS;
827845
}
846+
847+
private showTimeoutIcon() {
848+
this.srcHost.showDeviceIconFor(
849+
"tcp_timeout",
850+
"⏰",
851+
"TCP Timeout",
852+
2000,
853+
Layer.Transport,
854+
);
855+
}
828856
}
829857

830-
interface RetransmissionQueueItem {
858+
type RetransmissionQueueItem = DataSegment | "SYN";
859+
interface DataSegment {
831860
seqNum: number;
832861
size: number;
833862
}
@@ -845,14 +874,26 @@ class RetransmissionQueue {
845874
this.rttEstimator = rttEstimator;
846875
}
847876

877+
pushSyn() {
878+
this.itemQueue.unshift("SYN");
879+
this.startTimer();
880+
}
881+
848882
push(seqNum: number, size: number) {
849883
const item = { seqNum, size };
850884
this.itemQueue.push(item);
851-
this.itemQueue.sort((a, b) => a.seqNum - b.seqNum);
885+
this.itemQueue.sort((a, b) => {
886+
// SYN segments should always be at the front
887+
if (a === "SYN") {
888+
return -1;
889+
}
890+
if (b === "SYN") {
891+
return 1;
892+
}
893+
return a.seqNum - b.seqNum;
894+
});
852895

853-
if (!this.timeoutTick) {
854-
this.startTimer();
855-
}
896+
this.startTimer();
856897
}
857898

858899
/**
@@ -870,6 +911,10 @@ class RetransmissionQueue {
870911

871912
ack(ackNum: number) {
872913
this.itemQueue = this.itemQueue.filter((item) => {
914+
// We treat any valid ACK as a SYN ACK
915+
if (item === "SYN") {
916+
return false;
917+
}
873918
return !(
874919
item.seqNum < ackNum ||
875920
(item.seqNum + item.size) % u32_MODULUS <= ackNum
@@ -884,9 +929,10 @@ class RetransmissionQueue {
884929
if (this.itemQueue.length === 0) {
885930
return;
886931
}
887-
const firstSegmentItem = this.itemQueue[0];
888-
// Remove the segment from the queue
889-
this.ack(firstSegmentItem.seqNum + 1);
932+
const firstSegmentItem = this.itemQueue.shift();
933+
if (this.itemQueue.length === 0) {
934+
this.stopTimer();
935+
}
890936
return firstSegmentItem;
891937
}
892938

@@ -1162,6 +1208,12 @@ class RTTEstimator {
11621208
Ticker.shared.remove(this.measureTick, this);
11631209
}
11641210

1211+
restartMeasurement(seqNum: number) {
1212+
// Restart the measurement for the segment
1213+
this.discardMeasurement(seqNum);
1214+
this.startMeasurement(seqNum);
1215+
}
1216+
11651217
private measureTick(ticker: Ticker) {
11661218
// Update the current sample's RTT
11671219
// NOTE: we do this to account for the simulation's speed

0 commit comments

Comments
 (0)