RACK: Detect DSACK

Detect if the ACK is a duplicate and update in RACK.

PiperOrigin-RevId: 342332569
This commit is contained in:
Nayana Bidari
2020-11-13 13:59:43 -08:00
committed by gVisor bot
parent 6c0f53002a
commit 839dd97008
5 changed files with 470 additions and 22 deletions
+1
View File
@@ -82,6 +82,7 @@ type TCPRACKState struct {
FACK seqnum.Value
RTT time.Duration
Reord bool
DSACKSeen bool
}
// TCPEndpointID is the unique 4 tuple that identifies a given endpoint.
+1
View File
@@ -3051,6 +3051,7 @@ func (e *endpoint) completeState() stack.TCPEndpointState {
FACK: rc.fack,
RTT: rc.rtt,
Reord: rc.reorderSeen,
DSACKSeen: rc.dsackSeen,
}
return s
}
+8 -3
View File
@@ -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
}
+56 -8
View File
@@ -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) {
+404 -11
View File
@@ -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
}