Skip to content

Commit 37b166d

Browse files
authored
fix: ensure we send the last ACK on each TCP connection (#265)
This PR fixes an issue we had where sometimes the last ACK wasn't sent from the client side on a TCP connection. The issue was fixed by explicitly continuing the main loop until a segment with ACKing the FIN of a connection was sent.
1 parent 9200965 commit 37b166d

File tree

1 file changed

+16
-1
lines changed

1 file changed

+16
-1
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export class TcpState {
100100
// Buffer of data received
101101
private readBuffer = new BytesBuffer(MAX_BUFFER_SIZE);
102102
private readChannel = new AsyncQueue<number>();
103+
private readClosedSeqnum = -1;
103104
private readClosed = false;
104105

105106
// Buffer of data to be sent
@@ -443,6 +444,7 @@ export class TcpState {
443444
}
444445

445446
if (flags.fin) {
447+
this.readClosedSeqnum = this.recvNext;
446448
this.recvNext = (this.recvNext + 1) % u32_MODULUS;
447449
this.readClosed = true;
448450
this.readChannel.push(0);
@@ -646,27 +648,37 @@ export class TcpState {
646648
}
647649

648650
private async mainLoop() {
651+
let lastAcked = this.sendUnacknowledged;
652+
649653
let recheckPromise = this.cmdQueue.pop();
650654
let receivedSegmentPromise = this.tcpQueue.pop();
651655
let retransmitPromise = this.retransmissionQueue.pop();
652656

653-
while (!this.readClosed || !this.writeClosed) {
657+
while (
658+
!this.readClosed ||
659+
!this.writeClosed ||
660+
// This ensures we send the last ACK
661+
lastAcked !== this.readClosedSeqnum + 1
662+
) {
654663
const result = await Promise.race([
655664
recheckPromise,
656665
receivedSegmentPromise,
657666
retransmitPromise,
658667
]);
659668

660669
if (result === Command.SEND_ACK) {
670+
console.debug("[" + this.srcHost.id + "] [TCP] Processing SEND_ACK");
661671
recheckPromise = this.cmdQueue.pop();
662672
this.notifiedSendPackets = false;
663673
} else if (result === Command.ABORT) {
674+
console.debug("[" + this.srcHost.id + "] [TCP] Processing ABORT");
664675
// Send RST and quit
665676
const segment = this.newSegment(this.sendNext, 0);
666677
segment.withFlags(new Flags().withRst());
667678
sendIpPacket(this.srcHost, this.dstHost, segment);
668679
break;
669680
} else if ("segment" in result) {
681+
console.debug("[" + this.srcHost.id + "] [TCP] Processing segments");
670682
receivedSegmentPromise = this.tcpQueue.pop();
671683
let segment = result.segment;
672684

@@ -699,6 +711,7 @@ export class TcpState {
699711
}
700712
continue;
701713
} else if ("seqNum" in result) {
714+
console.debug("[" + this.srcHost.id + "] [TCP] Processing timeout");
702715
retransmitPromise = this.retransmissionQueue.pop();
703716
// Retransmit the segment
704717
this.resendPacket(result.seqNum, result.size);
@@ -709,6 +722,7 @@ export class TcpState {
709722
const segment = this.newSegment(this.sendNext, this.recvNext).withFlags(
710723
new Flags().withAck(),
711724
);
725+
lastAcked = this.recvNext;
712726

713727
const sendSize = Math.min(this.sendWindowSize(), MAX_SEGMENT_SIZE);
714728
if (sendSize > 0) {
@@ -728,6 +742,7 @@ export class TcpState {
728742
this.sendNext = (this.sendNext + 1) % u32_MODULUS;
729743
segment.flags.withFin();
730744
}
745+
console.debug("[" + this.srcHost.id + "] [TCP] sending segment", segment);
731746

732747
// Ignore failed sends
733748
if (sendIpPacket(this.srcHost, this.dstHost, segment)) {

0 commit comments

Comments
 (0)