From f3de211bb764d4e720879509debf918d37a71ce7 Mon Sep 17 00:00:00 2001 From: Nayana Bidari Date: Thu, 25 Feb 2021 16:27:30 -0800 Subject: RACK: recovery logic should check for receive window before re-transmitting. Use maybeSendSegment while sending segments in RACK recovery which checks if the receiver has space and splits the segments when the segment size is greater than MSS. PiperOrigin-RevId: 359641097 --- pkg/tcpip/transport/tcp/rack.go | 6 ++- pkg/tcpip/transport/tcp/tcp_rack_test.go | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/pkg/tcpip/transport/tcp/rack.go b/pkg/tcpip/transport/tcp/rack.go index ecabeceb4..0a0d5f7a1 100644 --- a/pkg/tcpip/transport/tcp/rack.go +++ b/pkg/tcpip/transport/tcp/rack.go @@ -475,9 +475,11 @@ func (rc *rackControl) DoRecovery(_ *segment, fastRetransmit bool) { break } - snd.outstanding++ + if sent := snd.maybeSendSegment(seg, int(snd.ep.scoreboard.SMSS()), snd.sndUna.Add(snd.sndWnd)); !sent { + break + } dataSent = true - snd.sendSegment(seg) + snd.outstanding += snd.pCount(seg, snd.maxPayloadSize) } snd.postXmit(dataSent, true /* shouldScheduleProbe */) diff --git a/pkg/tcpip/transport/tcp/tcp_rack_test.go b/pkg/tcpip/transport/tcp/tcp_rack_test.go index 3c13fc8a3..5cdd5b588 100644 --- a/pkg/tcpip/transport/tcp/tcp_rack_test.go +++ b/pkg/tcpip/transport/tcp/tcp_rack_test.go @@ -987,3 +987,71 @@ func TestRACKUpdateSackedOut(t *testing.T) { // test completes. <-probeDone } + +// TestRACKWithWindowFull tests that RACK honors the receive window size. +func TestRACKWithWindowFull(t *testing.T) { + c := context.New(t, uint32(mtu)) + defer c.Cleanup() + + setStackSACKPermitted(t, c, true) + setStackRACKPermitted(t, c) + createConnectedWithSACKAndTS(c) + + seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1) + const numPkts = 10 + data := make([]byte, numPkts*maxPayload) + for i := range data { + data[i] = byte(i) + } + + // Write the data. + var r bytes.Reader + r.Reset(data) + if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil { + t.Fatalf("Write failed: %s", err) + } + + bytesRead := 0 + for i := 0; i < numPkts; i++ { + c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize) + bytesRead += maxPayload + if i == 0 { + // Send ACK for the first packet to establish RTT. + c.SendAck(seq, maxPayload) + } + } + + // SACK for #10 packet. + start := c.IRS.Add(seqnum.Size(1 + (numPkts-1)*maxPayload)) + end := start.Add(seqnum.Size(maxPayload)) + c.SendAckWithSACK(seq, 2*maxPayload, []header.SACKBlock{{start, end}}) + + var info tcpip.TCPInfoOption + if err := c.EP.GetSockOpt(&info); err != nil { + t.Fatalf("GetSockOpt failed: %v", err) + } + // Wait for RTT to trigger recovery. + time.Sleep(info.RTT) + + // Expect retransmission of #2 packet. + c.ReceiveAndCheckPacketWithOptions(data, 2*maxPayload, maxPayload, tsOptionSize) + + // Send ACK for #2 packet. + c.SendAck(seq, 3*maxPayload) + + // Expect retransmission of #3 packet. + c.ReceiveAndCheckPacketWithOptions(data, 3*maxPayload, maxPayload, tsOptionSize) + + // Send ACK with zero window size. + c.SendPacket(nil, &context.Headers{ + SrcPort: context.TestPort, + DstPort: c.Port, + Flags: header.TCPFlagAck, + SeqNum: seq, + AckNum: c.IRS.Add(1 + 4*maxPayload), + RcvWnd: 0, + }) + + // No packet should be received as the receive window size is zero. + c.CheckNoPacket("unexpected packet received after userTimeout has expired") +} -- cgit v1.2.3