Skip to content

Commit d75fe76

Browse files
nybidarigvisor-bot
authored andcommitted
RACK: Detect packet reordering.
RACK detects packet reordering by checking if the sender received ACK for the packet which has the sequence number less than the already acknowledged packets. PiperOrigin-RevId: 336397526
1 parent 5389e44 commit d75fe76

File tree

6 files changed

+170
-20
lines changed

6 files changed

+170
-20
lines changed

pkg/tcpip/transport/tcp/endpoint.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3013,6 +3013,7 @@ func (e *endpoint) completeState() stack.TCPEndpointState {
30133013
EndSequence: rc.endSequence,
30143014
FACK: rc.fack,
30153015
RTT: rc.rtt,
3016+
Reord: rc.reorderSeen,
30163017
}
30173018
return s
30183019
}

pkg/tcpip/transport/tcp/rack.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ import (
2929
//
3030
// +stateify savable
3131
type rackControl struct {
32-
// xmitTime is the latest transmission timestamp of rackControl.seg.
33-
xmitTime time.Time `state:".(unixTime)"`
34-
3532
// endSequence is the ending TCP sequence number of rackControl.seg.
3633
endSequence seqnum.Value
3734

35+
// dsack indicates if the connection has seen a DSACK.
36+
dsack bool
37+
3838
// fack is the highest selectively or cumulatively acknowledged
3939
// sequence.
4040
fack seqnum.Value
@@ -47,11 +47,18 @@ type rackControl struct {
4747
// acknowledged) that was not marked invalid as a possible spurious
4848
// retransmission.
4949
rtt time.Duration
50+
51+
// reorderSeen indicates if reordering has been detected on this
52+
// connection.
53+
reorderSeen bool
54+
55+
// xmitTime is the latest transmission timestamp of rackControl.seg.
56+
xmitTime time.Time `state:".(unixTime)"`
5057
}
5158

52-
// Update will update the RACK related fields when an ACK has been received.
59+
// update will update the RACK related fields when an ACK has been received.
5360
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
54-
func (rc *rackControl) Update(seg *segment, ackSeg *segment, offset uint32) {
61+
func (rc *rackControl) update(seg *segment, ackSeg *segment, offset uint32) {
5562
rtt := time.Now().Sub(seg.xmitTime)
5663

5764
// If the ACK is for a retransmitted packet, do not update if it is a
@@ -92,3 +99,26 @@ func (rc *rackControl) Update(seg *segment, ackSeg *segment, offset uint32) {
9299
rc.endSequence = endSeq
93100
}
94101
}
102+
103+
// detectReorder detects if packet reordering has been observed.
104+
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
105+
// * Step 3: Detect data segment reordering.
106+
// To detect reordering, the sender looks for original data segments being
107+
// delivered out of order. To detect such cases, the sender tracks the
108+
// highest sequence selectively or cumulatively acknowledged in the RACK.fack
109+
// variable. The name "fack" stands for the most "Forward ACK" (this term is
110+
// adopted from [FACK]). If a never retransmitted segment that's below
111+
// RACK.fack is (selectively or cumulatively) acknowledged, it has been
112+
// delivered out of order. The sender sets RACK.reord to TRUE if such segment
113+
// is identified.
114+
func (rc *rackControl) detectReorder(seg *segment) {
115+
endSeq := seg.sequenceNumber.Add(seqnum.Size(seg.data.Size()))
116+
if rc.fack.LessThan(endSeq) {
117+
rc.fack = endSeq
118+
return
119+
}
120+
121+
if endSeq.LessThan(rc.fack) && seg.xmitCount == 1 {
122+
rc.reorderSeen = true
123+
}
124+
}

pkg/tcpip/transport/tcp/segment.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ type segment struct {
7171
// xmitTime is the last transmit time of this segment.
7272
xmitTime time.Time `state:".(unixTime)"`
7373
xmitCount uint32
74+
75+
// acked indicates if the segment has already been SACKed.
76+
acked bool
7477
}
7578

7679
func newSegment(r *stack.Route, id stack.TransportEndpointID, pkt *stack.PacketBuffer) *segment {

pkg/tcpip/transport/tcp/snd.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package tcp
1717
import (
1818
"fmt"
1919
"math"
20+
"sort"
2021
"sync/atomic"
2122
"time"
2223

@@ -263,6 +264,9 @@ func newSender(ep *endpoint, iss, irs seqnum.Value, sndWnd seqnum.Size, mss uint
263264
highRxt: iss,
264265
rescueRxt: iss,
265266
},
267+
rc: rackControl{
268+
fack: iss,
269+
},
266270
gso: ep.gso != nil,
267271
}
268272

@@ -1274,6 +1278,39 @@ func (s *sender) checkDuplicateAck(seg *segment) (rtx bool) {
12741278
return true
12751279
}
12761280

1281+
// Iterate the writeList and update RACK for each segment which is newly acked
1282+
// either cumulatively or selectively. Loop through the segments which are
1283+
// sacked, and update the RACK related variables and check for reordering.
1284+
//
1285+
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
1286+
// steps 2 and 3.
1287+
func (s *sender) walkSACK(rcvdSeg *segment) {
1288+
// Sort the SACK blocks. The first block is the most recent unacked
1289+
// block. The following blocks can be in arbitrary order.
1290+
sackBlocks := make([]header.SACKBlock, len(rcvdSeg.parsedOptions.SACKBlocks))
1291+
copy(sackBlocks, rcvdSeg.parsedOptions.SACKBlocks)
1292+
sort.Slice(sackBlocks, func(i, j int) bool {
1293+
return sackBlocks[j].Start.LessThan(sackBlocks[i].Start)
1294+
})
1295+
1296+
seg := s.writeList.Front()
1297+
for _, sb := range sackBlocks {
1298+
// This check excludes DSACK blocks.
1299+
if sb.Start.LessThanEq(rcvdSeg.ackNumber) || sb.Start.LessThanEq(s.sndUna) || s.sndNxt.LessThan(sb.End) {
1300+
continue
1301+
}
1302+
1303+
for seg != nil && seg.sequenceNumber.LessThan(sb.End) && seg.xmitCount != 0 {
1304+
if sb.Start.LessThanEq(seg.sequenceNumber) && !seg.acked {
1305+
s.rc.update(seg, rcvdSeg, s.ep.tsOffset)
1306+
s.rc.detectReorder(seg)
1307+
seg.acked = true
1308+
}
1309+
seg = seg.Next()
1310+
}
1311+
}
1312+
}
1313+
12771314
// handleRcvdSegment is called when a segment is received; it is responsible for
12781315
// updating the send-related state.
12791316
func (s *sender) handleRcvdSegment(rcvdSeg *segment) {
@@ -1308,6 +1345,21 @@ func (s *sender) handleRcvdSegment(rcvdSeg *segment) {
13081345
rcvdSeg.hasNewSACKInfo = true
13091346
}
13101347
}
1348+
1349+
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08
1350+
// section-7.2
1351+
// * Step 2: Update RACK stats.
1352+
// If the ACK is not ignored as invalid, update the RACK.rtt
1353+
// to be the RTT sample calculated using this ACK, and
1354+
// continue. If this ACK or SACK was for the most recently
1355+
// sent packet, then record the RACK.xmit_ts timestamp and
1356+
// RACK.end_seq sequence implied by this ACK.
1357+
// * Step 3: Detect packet reordering.
1358+
// If the ACK selectively or cumulatively acknowledges an
1359+
// unacknowledged and also never retransmitted sequence below
1360+
// RACK.fack, then the corresponding packet has been
1361+
// reordered and RACK.reord is set to TRUE.
1362+
s.walkSACK(rcvdSeg)
13111363
s.SetPipe()
13121364
}
13131365

@@ -1385,13 +1437,14 @@ func (s *sender) handleRcvdSegment(rcvdSeg *segment) {
13851437
}
13861438

13871439
// Update the RACK fields if SACK is enabled.
1388-
if s.ep.sackPermitted {
1389-
s.rc.Update(seg, rcvdSeg, s.ep.tsOffset)
1440+
if s.ep.sackPermitted && !seg.acked {
1441+
s.rc.update(seg, rcvdSeg, s.ep.tsOffset)
1442+
s.rc.detectReorder(seg)
13901443
}
13911444

13921445
s.writeList.Remove(seg)
13931446

1394-
// if SACK is enabled then Only reduce outstanding if
1447+
// If SACK is enabled then Only reduce outstanding if
13951448
// the segment was not previously SACKED as these have
13961449
// already been accounted for in SetPipe().
13971450
if !s.ep.sackPermitted || !s.ep.scoreboard.IsSACKED(seg.sackBlock()) {

pkg/tcpip/transport/tcp/tcp_rack_test.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,20 @@ import (
2121
"gvisor.dev/gvisor/pkg/tcpip"
2222
"gvisor.dev/gvisor/pkg/tcpip/buffer"
2323
"gvisor.dev/gvisor/pkg/tcpip/header"
24+
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
2425
"gvisor.dev/gvisor/pkg/tcpip/stack"
2526
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
2627
)
2728

29+
const (
30+
maxPayload = 10
31+
tsOptionSize = 12
32+
maxTCPOptionSize = 40
33+
)
34+
2835
// TestRACKUpdate tests the RACK related fields are updated when an ACK is
2936
// received on a SACK enabled connection.
3037
func TestRACKUpdate(t *testing.T) {
31-
const maxPayload = 10
32-
const tsOptionSize = 12
33-
const maxTCPOptionSize = 40
34-
3538
c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
3639
defer c.Cleanup()
3740

@@ -49,7 +52,7 @@ func TestRACKUpdate(t *testing.T) {
4952
}
5053

5154
if state.Sender.RACKState.RTT == 0 {
52-
t.Fatalf("RACK RTT failed to update when an ACK is received")
55+
t.Fatalf("RACK RTT failed to update when an ACK is received, got RACKState.RTT == 0 want != 0")
5356
}
5457
})
5558
setStackSACKPermitted(t, c, true)
@@ -69,6 +72,66 @@ func TestRACKUpdate(t *testing.T) {
6972
bytesRead := 0
7073
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
7174
bytesRead += maxPayload
72-
c.SendAck(790, bytesRead)
75+
c.SendAck(seqnum.Value(context.TestInitialSequenceNumber).Add(1), bytesRead)
7376
time.Sleep(200 * time.Millisecond)
7477
}
78+
79+
// TestRACKDetectReorder tests that RACK detects packet reordering.
80+
func TestRACKDetectReorder(t *testing.T) {
81+
c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
82+
defer c.Cleanup()
83+
84+
const ackNum = 2
85+
86+
var n int
87+
ch := make(chan struct{})
88+
c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
89+
gotSeq := state.Sender.RACKState.FACK
90+
wantSeq := state.Sender.SndNxt
91+
// FACK should be updated to the highest ending sequence number of the
92+
// segment acknowledged most recently.
93+
if !gotSeq.LessThanEq(wantSeq) || gotSeq.LessThan(wantSeq) {
94+
t.Fatalf("RACK FACK failed to update, got: %v, but want: %v", gotSeq, wantSeq)
95+
}
96+
97+
n++
98+
if n < ackNum {
99+
if state.Sender.RACKState.Reord {
100+
t.Fatalf("RACK reorder detected when there is no reordering")
101+
}
102+
return
103+
}
104+
105+
if state.Sender.RACKState.Reord == false {
106+
t.Fatalf("RACK reorder detection failed")
107+
}
108+
close(ch)
109+
})
110+
setStackSACKPermitted(t, c, true)
111+
createConnectedWithSACKAndTS(c)
112+
data := buffer.NewView(ackNum * maxPayload)
113+
for i := range data {
114+
data[i] = byte(i)
115+
}
116+
117+
// Write the data.
118+
if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
119+
t.Fatalf("Write failed: %s", err)
120+
}
121+
122+
bytesRead := 0
123+
for i := 0; i < ackNum; i++ {
124+
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
125+
bytesRead += maxPayload
126+
}
127+
128+
start := c.IRS.Add(maxPayload + 1)
129+
end := start.Add(maxPayload)
130+
seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
131+
c.SendAckWithSACK(seq, 0, []header.SACKBlock{{start, end}})
132+
c.SendAck(seq, bytesRead)
133+
134+
// Wait for the probe function to finish processing the ACK before the
135+
// test completes.
136+
<-ch
137+
}

pkg/tcpip/transport/tcp/testing/context/context.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ const (
6868
// V4MappedWildcardAddr is the mapped v6 representation of 0.0.0.0.
6969
V4MappedWildcardAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00"
7070

71-
// testInitialSequenceNumber is the initial sequence number sent in packets that
71+
// TestInitialSequenceNumber is the initial sequence number sent in packets that
7272
// are sent in response to a SYN or in the initial SYN sent to the stack.
73-
testInitialSequenceNumber = 789
73+
TestInitialSequenceNumber = 789
7474
)
7575

7676
// StackAddrWithPrefix is StackAddr with its associated prefix length.
@@ -505,7 +505,7 @@ func (c *Context) ReceiveAndCheckPacketWithOptions(data []byte, offset, size, op
505505
checker.TCP(
506506
checker.DstPort(TestPort),
507507
checker.TCPSeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))),
508-
checker.TCPAckNum(uint32(seqnum.Value(testInitialSequenceNumber).Add(1))),
508+
checker.TCPAckNum(uint32(seqnum.Value(TestInitialSequenceNumber).Add(1))),
509509
checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
510510
),
511511
)
@@ -532,7 +532,7 @@ func (c *Context) ReceiveNonBlockingAndCheckPacket(data []byte, offset, size int
532532
checker.TCP(
533533
checker.DstPort(TestPort),
534534
checker.TCPSeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))),
535-
checker.TCPAckNum(uint32(seqnum.Value(testInitialSequenceNumber).Add(1))),
535+
checker.TCPAckNum(uint32(seqnum.Value(TestInitialSequenceNumber).Add(1))),
536536
checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
537537
),
538538
)
@@ -912,7 +912,7 @@ func (c *Context) CreateConnectedWithOptions(wantOptions header.TCPSynOptions) *
912912

913913
// Build SYN-ACK.
914914
c.IRS = seqnum.Value(tcpSeg.SequenceNumber())
915-
iss := seqnum.Value(testInitialSequenceNumber)
915+
iss := seqnum.Value(TestInitialSequenceNumber)
916916
c.SendPacket(nil, &Headers{
917917
SrcPort: tcpSeg.DestinationPort(),
918918
DstPort: tcpSeg.SourcePort(),
@@ -1084,7 +1084,7 @@ func (c *Context) PassiveConnectWithOptions(maxPayload, wndScale int, synOptions
10841084
offset += paddingToAdd
10851085

10861086
// Send a SYN request.
1087-
iss := seqnum.Value(testInitialSequenceNumber)
1087+
iss := seqnum.Value(TestInitialSequenceNumber)
10881088
c.SendPacket(nil, &Headers{
10891089
SrcPort: TestPort,
10901090
DstPort: StackPort,

0 commit comments

Comments
 (0)