summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/transport
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/tcpip/transport')
-rw-r--r--pkg/tcpip/transport/tcp/endpoint.go1
-rw-r--r--pkg/tcpip/transport/tcp/rack.go11
-rw-r--r--pkg/tcpip/transport/tcp/snd.go64
-rw-r--r--pkg/tcpip/transport/tcp/tcp_rack_test.go415
4 files changed, 469 insertions, 22 deletions
diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go
index 194d3a8a4..e78138415 100644
--- a/pkg/tcpip/transport/tcp/endpoint.go
+++ b/pkg/tcpip/transport/tcp/endpoint.go
@@ -3051,6 +3051,7 @@ func (e *endpoint) completeState() stack.TCPEndpointState {
FACK: rc.fack,
RTT: rc.rtt,
Reord: rc.reorderSeen,
+ DSACKSeen: rc.dsackSeen,
}
return s
}
diff --git a/pkg/tcpip/transport/tcp/rack.go b/pkg/tcpip/transport/tcp/rack.go
index d312b1b8b..e0a50a919 100644
--- a/pkg/tcpip/transport/tcp/rack.go
+++ b/pkg/tcpip/transport/tcp/rack.go
@@ -29,12 +29,12 @@ import (
//
// +stateify savable
type rackControl struct {
+ // dsackSeen indicates if the connection has seen a DSACK.
+ dsackSeen bool
+
// endSequence is the ending TCP sequence number of rackControl.seg.
endSequence seqnum.Value
- // dsack indicates if the connection has seen a DSACK.
- dsack bool
-
// fack is the highest selectively or cumulatively acknowledged
// sequence.
fack seqnum.Value
@@ -122,3 +122,8 @@ func (rc *rackControl) detectReorder(seg *segment) {
rc.reorderSeen = true
}
}
+
+// setDSACKSeen updates rack control if duplicate SACK is seen by the connection.
+func (rc *rackControl) setDSACKSeen() {
+ rc.dsackSeen = true
+}
diff --git a/pkg/tcpip/transport/tcp/snd.go b/pkg/tcpip/transport/tcp/snd.go
index c42d7159a..201cf9aa9 100644
--- a/pkg/tcpip/transport/tcp/snd.go
+++ b/pkg/tcpip/transport/tcp/snd.go
@@ -1182,25 +1182,29 @@ func (s *sender) detectLoss(seg *segment) (fastRetransmit bool) {
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
// steps 2 and 3.
func (s *sender) walkSACK(rcvdSeg *segment) {
- if len(rcvdSeg.parsedOptions.SACKBlocks) == 0 {
+ // Look for DSACK block.
+ idx := 0
+ n := len(rcvdSeg.parsedOptions.SACKBlocks)
+ if s.checkDSACK(rcvdSeg) {
+ s.rc.setDSACKSeen()
+ idx = 1
+ n--
+ }
+
+ if n == 0 {
return
}
// Sort the SACK blocks. The first block is the most recent unacked
// block. The following blocks can be in arbitrary order.
- sackBlocks := make([]header.SACKBlock, len(rcvdSeg.parsedOptions.SACKBlocks))
- copy(sackBlocks, rcvdSeg.parsedOptions.SACKBlocks)
+ sackBlocks := make([]header.SACKBlock, n)
+ copy(sackBlocks, rcvdSeg.parsedOptions.SACKBlocks[idx:])
sort.Slice(sackBlocks, func(i, j int) bool {
return sackBlocks[j].Start.LessThan(sackBlocks[i].Start)
})
seg := s.writeList.Front()
for _, sb := range sackBlocks {
- // This check excludes DSACK blocks.
- if sb.Start.LessThanEq(rcvdSeg.ackNumber) || sb.Start.LessThanEq(s.sndUna) || s.sndNxt.LessThan(sb.End) {
- continue
- }
-
for seg != nil && seg.sequenceNumber.LessThan(sb.End) && seg.xmitCount != 0 {
if sb.Start.LessThanEq(seg.sequenceNumber) && !seg.acked {
s.rc.update(seg, rcvdSeg, s.ep.tsOffset)
@@ -1212,6 +1216,50 @@ func (s *sender) walkSACK(rcvdSeg *segment) {
}
}
+// checkDSACK checks if a DSACK is reported and updates it in RACK.
+func (s *sender) checkDSACK(rcvdSeg *segment) bool {
+ n := len(rcvdSeg.parsedOptions.SACKBlocks)
+ if n == 0 {
+ return false
+ }
+
+ sb := rcvdSeg.parsedOptions.SACKBlocks[0]
+ // Check if SACK block is invalid.
+ if sb.End.LessThan(sb.Start) {
+ return false
+ }
+
+ // See: https://tools.ietf.org/html/rfc2883#section-5 DSACK is sent in
+ // at most one SACK block. DSACK is detected in the below two cases:
+ // * If the SACK sequence space is less than this cumulative ACK, it is
+ // an indication that the segment identified by the SACK block has
+ // been received more than once by the receiver.
+ // * If the sequence space in the first SACK block is greater than the
+ // cumulative ACK, then the sender next compares the sequence space
+ // in the first SACK block with the sequence space in the second SACK
+ // block, if there is one. This comparison can determine if the first
+ // SACK block is reporting duplicate data that lies above the
+ // cumulative ACK.
+ if sb.Start.LessThan(rcvdSeg.ackNumber) {
+ return true
+ }
+
+ if n > 1 {
+ sb1 := rcvdSeg.parsedOptions.SACKBlocks[1]
+ if sb1.End.LessThan(sb1.Start) {
+ return false
+ }
+
+ // If the first SACK block is fully covered by second SACK
+ // block, then the first block is a DSACK block.
+ if sb.End.LessThanEq(sb1.End) && sb1.Start.LessThanEq(sb.Start) {
+ return true
+ }
+ }
+
+ return false
+}
+
// handleRcvdSegment is called when a segment is received; it is responsible for
// updating the send-related state.
func (s *sender) handleRcvdSegment(rcvdSeg *segment) {
diff --git a/pkg/tcpip/transport/tcp/tcp_rack_test.go b/pkg/tcpip/transport/tcp/tcp_rack_test.go
index d3f92b48c..9818ffa0f 100644
--- a/pkg/tcpip/transport/tcp/tcp_rack_test.go
+++ b/pkg/tcpip/transport/tcp/tcp_rack_test.go
@@ -30,15 +30,17 @@ const (
maxPayload = 10
tsOptionSize = 12
maxTCPOptionSize = 40
+ mtu = header.TCPMinimumSize + header.IPv4MinimumSize + maxTCPOptionSize + maxPayload
)
// TestRACKUpdate tests the RACK related fields are updated when an ACK is
// received on a SACK enabled connection.
func TestRACKUpdate(t *testing.T) {
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
+ c := context.New(t, uint32(mtu))
defer c.Cleanup()
var xmitTime time.Time
+ probeDone := make(chan struct{})
c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
// Validate that the endpoint Sender.RACKState is what we expect.
if state.Sender.RACKState.XmitTime.Before(xmitTime) {
@@ -54,6 +56,7 @@ func TestRACKUpdate(t *testing.T) {
if state.Sender.RACKState.RTT == 0 {
t.Fatalf("RACK RTT failed to update when an ACK is received, got RACKState.RTT == 0 want != 0")
}
+ close(probeDone)
})
setStackSACKPermitted(t, c, true)
createConnectedWithSACKAndTS(c)
@@ -73,18 +76,20 @@ func TestRACKUpdate(t *testing.T) {
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
bytesRead += maxPayload
c.SendAck(seqnum.Value(context.TestInitialSequenceNumber).Add(1), bytesRead)
- time.Sleep(200 * time.Millisecond)
+
+ // Wait for the probe function to finish processing the ACK before the
+ // test completes.
+ <-probeDone
}
// TestRACKDetectReorder tests that RACK detects packet reordering.
func TestRACKDetectReorder(t *testing.T) {
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
+ c := context.New(t, uint32(mtu))
defer c.Cleanup()
- const ackNum = 2
-
var n int
- ch := make(chan struct{})
+ const ackNumToVerify = 2
+ probeDone := make(chan struct{})
c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
gotSeq := state.Sender.RACKState.FACK
wantSeq := state.Sender.SndNxt
@@ -95,7 +100,7 @@ func TestRACKDetectReorder(t *testing.T) {
}
n++
- if n < ackNum {
+ if n < ackNumToVerify {
if state.Sender.RACKState.Reord {
t.Fatalf("RACK reorder detected when there is no reordering")
}
@@ -105,11 +110,11 @@ func TestRACKDetectReorder(t *testing.T) {
if state.Sender.RACKState.Reord == false {
t.Fatalf("RACK reorder detection failed")
}
- close(ch)
+ close(probeDone)
})
setStackSACKPermitted(t, c, true)
createConnectedWithSACKAndTS(c)
- data := buffer.NewView(ackNum * maxPayload)
+ data := buffer.NewView(ackNumToVerify * maxPayload)
for i := range data {
data[i] = byte(i)
}
@@ -120,7 +125,7 @@ func TestRACKDetectReorder(t *testing.T) {
}
bytesRead := 0
- for i := 0; i < ackNum; i++ {
+ for i := 0; i < ackNumToVerify; i++ {
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
bytesRead += maxPayload
}
@@ -133,5 +138,393 @@ func TestRACKDetectReorder(t *testing.T) {
// Wait for the probe function to finish processing the ACK before the
// test completes.
- <-ch
+ <-probeDone
+}
+
+func sendAndReceive(t *testing.T, c *context.Context, numPackets int) buffer.View {
+ setStackSACKPermitted(t, c, true)
+ createConnectedWithSACKAndTS(c)
+
+ data := buffer.NewView(numPackets * maxPayload)
+ for i := range data {
+ data[i] = byte(i)
+ }
+
+ // Write the data.
+ if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
+ t.Fatalf("Write failed: %s", err)
+ }
+
+ bytesRead := 0
+ for i := 0; i < numPackets; i++ {
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+ bytesRead += maxPayload
+ }
+
+ return data
+}
+
+const (
+ validDSACKDetected = 1
+ failedToDetectDSACK = 2
+ invalidDSACKDetected = 3
+)
+
+func addDSACKSeenCheckerProbe(t *testing.T, c *context.Context, numACK int, probeDone chan int) {
+ var n int
+ c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
+ // Validate that RACK detects DSACK.
+ n++
+ if n < numACK {
+ if state.Sender.RACKState.DSACKSeen {
+ probeDone <- invalidDSACKDetected
+ }
+ return
+ }
+
+ if !state.Sender.RACKState.DSACKSeen {
+ probeDone <- failedToDetectDSACK
+ return
+ }
+ probeDone <- validDSACKDetected
+ })
+}
+
+// TestRACKDetectDSACK tests that RACK detects DSACK with duplicate segments.
+// See: https://tools.ietf.org/html/rfc2883#section-4.1.1.
+func TestRACKDetectDSACK(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 2
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 8
+ data := sendAndReceive(t, c, numPackets)
+
+ // Cumulative ACK for [1-5] packets.
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ bytesRead := 5 * maxPayload
+ c.SendAck(seq, bytesRead)
+
+ // Expect retransmission of #6 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // Send DSACK block for #6 packet indicating both
+ // initial and retransmitted packet are received and
+ // packets [1-7] are received.
+ start := c.IRS.Add(seqnum.Size(bytesRead))
+ end := start.Add(maxPayload)
+ bytesRead += 2 * maxPayload
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Wait for the probe function to finish processing the
+ // ACK before the test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKDetectDSACKWithOutOfOrder tests that RACK detects DSACK with out of
+// order segments.
+// See: https://tools.ietf.org/html/rfc2883#section-4.1.2.
+func TestRACKDetectDSACKWithOutOfOrder(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 2
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 10
+ data := sendAndReceive(t, c, numPackets)
+
+ // Cumulative ACK for [1-5] packets.
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ bytesRead := 5 * maxPayload
+ c.SendAck(seq, bytesRead)
+
+ // Expect retransmission of #6 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // Send DSACK block for #6 packet indicating both
+ // initial and retransmitted packet are received and
+ // packets [1-7] are received.
+ start := c.IRS.Add(seqnum.Size(bytesRead))
+ end := start.Add(maxPayload)
+ bytesRead += 2 * maxPayload
+ // Send DSACK block for #6 along with out of
+ // order #9 packet is received.
+ start1 := c.IRS.Add(seqnum.Size(bytesRead) + maxPayload)
+ end1 := start1.Add(maxPayload)
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}, {start1, end1}})
+
+ // Wait for the probe function to finish processing the
+ // ACK before the test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKDetectDSACKWithOutOfOrderDup tests that DSACK is detected on a
+// duplicate of out of order packet.
+// See: https://tools.ietf.org/html/rfc2883#section-4.1.3
+func TestRACKDetectDSACKWithOutOfOrderDup(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 4
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 10
+ sendAndReceive(t, c, numPackets)
+
+ // ACK [1-5] packets.
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ bytesRead := 5 * maxPayload
+ c.SendAck(seq, bytesRead)
+
+ // Send SACK indicating #6 packet is missing and received #7 packet.
+ offset := seqnum.Size(bytesRead + maxPayload)
+ start := c.IRS.Add(1 + offset)
+ end := start.Add(maxPayload)
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Send SACK with #6 packet is missing and received [7-8] packets.
+ end = start.Add(2 * maxPayload)
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Consider #8 packet is duplicated on the network and send DSACK.
+ dsackStart := c.IRS.Add(1 + offset + maxPayload)
+ dsackEnd := dsackStart.Add(maxPayload)
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{dsackStart, dsackEnd}, {start, end}})
+
+ // Wait for the probe function to finish processing the ACK before the
+ // test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKDetectDSACKSingleDup tests DSACK for a single duplicate subsegment.
+// See: https://tools.ietf.org/html/rfc2883#section-4.2.1.
+func TestRACKDetectDSACKSingleDup(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 4
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 4
+ data := sendAndReceive(t, c, numPackets)
+
+ // Send ACK for #1 packet.
+ bytesRead := maxPayload
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ c.SendAck(seq, bytesRead)
+
+ // Missing [2-3] packets and received #4 packet.
+ seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ start := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
+ end := start.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Expect retransmission of #2 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // ACK for retransmitted #2 packet.
+ bytesRead += maxPayload
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Simulate receving delayed subsegment of #2 packet and delayed #3 packet by
+ // sending DSACK block for the subsegment.
+ dsackStart := c.IRS.Add(1 + seqnum.Size(bytesRead))
+ dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
+ c.SendAckWithSACK(seq, numPackets*maxPayload, []header.SACKBlock{{dsackStart, dsackEnd}})
+
+ // Wait for the probe function to finish processing the ACK before the
+ // test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKDetectDSACKDupWithCumulativeACK tests DSACK for two non-contiguous
+// duplicate subsegments covered by the cumulative acknowledgement.
+// See: https://tools.ietf.org/html/rfc2883#section-4.2.2.
+func TestRACKDetectDSACKDupWithCumulativeACK(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 5
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 6
+ data := sendAndReceive(t, c, numPackets)
+
+ // Send ACK for #1 packet.
+ bytesRead := maxPayload
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ c.SendAck(seq, bytesRead)
+
+ // Missing [2-5] packets and received #6 packet.
+ seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ start := c.IRS.Add(1 + seqnum.Size(5*maxPayload))
+ end := start.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Expect retransmission of #2 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // Received delayed #2 packet.
+ bytesRead += maxPayload
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Received delayed #4 packet.
+ start1 := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
+ end1 := start1.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
+
+ // Simulate receiving retransmitted subsegment for #2 packet and delayed #3
+ // packet by sending DSACK block for #2 packet.
+ dsackStart := c.IRS.Add(1 + seqnum.Size(maxPayload))
+ dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
+ c.SendAckWithSACK(seq, 4*maxPayload, []header.SACKBlock{{dsackStart, dsackEnd}, {start, end}})
+
+ // Wait for the probe function to finish processing the ACK before the
+ // test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKDetectDSACKDup tests two non-contiguous duplicate subsegments not
+// covered by the cumulative acknowledgement.
+// See: https://tools.ietf.org/html/rfc2883#section-4.2.3.
+func TestRACKDetectDSACKDup(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan int)
+ const ackNumToVerify = 5
+ addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
+
+ numPackets := 7
+ data := sendAndReceive(t, c, numPackets)
+
+ // Send ACK for #1 packet.
+ bytesRead := maxPayload
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ c.SendAck(seq, bytesRead)
+
+ // Missing [2-6] packets and SACK #7 packet.
+ seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ start := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
+ end := start.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
+
+ // Received delayed #3 packet.
+ start1 := c.IRS.Add(1 + seqnum.Size(2*maxPayload))
+ end1 := start1.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
+
+ // Expect retransmission of #2 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // Consider #2 packet has been dropped and SACK #4 packet.
+ start2 := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
+ end2 := start2.Add(seqnum.Size(maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start2, end2}, {start1, end1}, {start, end}})
+
+ // Simulate receiving retransmitted subsegment for #3 packet and delayed #5
+ // packet by sending DSACK block for the subsegment.
+ dsackStart := c.IRS.Add(1 + seqnum.Size(2*maxPayload))
+ dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
+ end1 = end1.Add(seqnum.Size(2 * maxPayload))
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{dsackStart, dsackEnd}, {start1, end1}})
+
+ // Wait for the probe function to finish processing the ACK before the
+ // test completes.
+ err := <-probeDone
+ switch err {
+ case failedToDetectDSACK:
+ t.Fatalf("RACK DSACK detection failed")
+ case invalidDSACKDetected:
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+}
+
+// TestRACKWithInvalidDSACKBlock tests that DSACK is not detected when DSACK
+// is not the first SACK block.
+func TestRACKWithInvalidDSACKBlock(t *testing.T) {
+ c := context.New(t, uint32(mtu))
+ defer c.Cleanup()
+
+ probeDone := make(chan struct{})
+ const ackNumToVerify = 2
+ var n int
+ c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
+ // Validate that RACK does not detect DSACK when DSACK block is
+ // not the first SACK block.
+ n++
+ t.Helper()
+ if state.Sender.RACKState.DSACKSeen {
+ t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
+ }
+
+ if n == ackNumToVerify {
+ close(probeDone)
+ }
+ })
+
+ numPackets := 10
+ data := sendAndReceive(t, c, numPackets)
+
+ // Cumulative ACK for [1-5] packets.
+ seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
+ bytesRead := 5 * maxPayload
+ c.SendAck(seq, bytesRead)
+
+ // Expect retransmission of #6 packet.
+ c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
+
+ // Send DSACK block for #6 packet indicating both
+ // initial and retransmitted packet are received and
+ // packets [1-7] are received.
+ start := c.IRS.Add(seqnum.Size(bytesRead))
+ end := start.Add(maxPayload)
+ bytesRead += 2 * maxPayload
+
+ // Send DSACK block as second block.
+ start1 := c.IRS.Add(seqnum.Size(bytesRead) + maxPayload)
+ end1 := start1.Add(maxPayload)
+ c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
+
+ // Wait for the probe function to finish processing the
+ // ACK before the test completes.
+ <-probeDone
}