mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Update variables for implementation of RACK in TCP
RACK (Recent Acknowledgement) is a new loss detection algorithm in TCP. These are the fields which should be stored on connections to implement RACK algorithm. PiperOrigin-RevId: 324948703
This commit is contained in:
committed by
gVisor bot
parent
87ee3898f7
commit
0e6f7a12c2
@@ -73,6 +73,16 @@ type TCPCubicState struct {
|
||||
WEst float64
|
||||
}
|
||||
|
||||
// TCPRACKState is used to hold a copy of the internal RACK state when the
|
||||
// TCPProbeFunc is invoked.
|
||||
type TCPRACKState struct {
|
||||
XmitTime time.Time
|
||||
EndSequence seqnum.Value
|
||||
FACK seqnum.Value
|
||||
RTT time.Duration
|
||||
Reord bool
|
||||
}
|
||||
|
||||
// TCPEndpointID is the unique 4 tuple that identifies a given endpoint.
|
||||
type TCPEndpointID struct {
|
||||
// LocalPort is the local port associated with the endpoint.
|
||||
@@ -212,6 +222,9 @@ type TCPSenderState struct {
|
||||
|
||||
// Cubic holds the state related to CUBIC congestion control.
|
||||
Cubic TCPCubicState
|
||||
|
||||
// RACKState holds the state related to RACK loss detection algorithm.
|
||||
RACKState TCPRACKState
|
||||
}
|
||||
|
||||
// TCPSACKInfo holds TCP SACK related information for a given TCP endpoint.
|
||||
|
||||
@@ -40,6 +40,8 @@ go_library(
|
||||
"endpoint_state.go",
|
||||
"forwarder.go",
|
||||
"protocol.go",
|
||||
"rack.go",
|
||||
"rack_state.go",
|
||||
"rcv.go",
|
||||
"rcv_state.go",
|
||||
"reno.go",
|
||||
@@ -83,6 +85,7 @@ go_test(
|
||||
"dual_stack_test.go",
|
||||
"sack_scoreboard_test.go",
|
||||
"tcp_noracedetector_test.go",
|
||||
"tcp_rack_test.go",
|
||||
"tcp_sack_test.go",
|
||||
"tcp_test.go",
|
||||
"tcp_timestamp_test.go",
|
||||
|
||||
@@ -521,7 +521,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
|
||||
synOpts := header.TCPSynOptions{
|
||||
WS: -1,
|
||||
TS: opts.TS,
|
||||
TSVal: tcpTimeStamp(timeStampOffset()),
|
||||
TSVal: tcpTimeStamp(time.Now(), timeStampOffset()),
|
||||
TSEcr: opts.TSVal,
|
||||
MSS: mssForRoute(&s.route),
|
||||
}
|
||||
|
||||
@@ -1166,13 +1166,18 @@ func (e *endpoint) handleSegments(fastPath bool) *tcpip.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSegment handles a given segment and notifies the worker goroutine if
|
||||
// if the connection should be terminated.
|
||||
func (e *endpoint) handleSegment(s *segment) (cont bool, err *tcpip.Error) {
|
||||
// Invoke the tcp probe if installed.
|
||||
func (e *endpoint) probeSegment() {
|
||||
if e.probe != nil {
|
||||
e.probe(e.completeState())
|
||||
}
|
||||
}
|
||||
|
||||
// handleSegment handles a given segment and notifies the worker goroutine if
|
||||
// if the connection should be terminated.
|
||||
func (e *endpoint) handleSegment(s *segment) (cont bool, err *tcpip.Error) {
|
||||
// Invoke the tcp probe if installed. The tcp probe function will update
|
||||
// the TCPEndpointState after the segment is processed.
|
||||
defer e.probeSegment()
|
||||
|
||||
if s.flagIsSet(header.TCPFlagRst) {
|
||||
if ok, err := e.handleReset(s); !ok {
|
||||
|
||||
@@ -2692,15 +2692,14 @@ func (e *endpoint) maybeEnableTimestamp(synOpts *header.TCPSynOptions) {
|
||||
// timestamp returns the timestamp value to be used in the TSVal field of the
|
||||
// timestamp option for outgoing TCP segments for a given endpoint.
|
||||
func (e *endpoint) timestamp() uint32 {
|
||||
return tcpTimeStamp(e.tsOffset)
|
||||
return tcpTimeStamp(time.Now(), e.tsOffset)
|
||||
}
|
||||
|
||||
// tcpTimeStamp returns a timestamp offset by the provided offset. This is
|
||||
// not inlined above as it's used when SYN cookies are in use and endpoint
|
||||
// is not created at the time when the SYN cookie is sent.
|
||||
func tcpTimeStamp(offset uint32) uint32 {
|
||||
now := time.Now()
|
||||
return uint32(now.Unix()*1000+int64(now.Nanosecond()/1e6)) + offset
|
||||
func tcpTimeStamp(curTime time.Time, offset uint32) uint32 {
|
||||
return uint32(curTime.Unix()*1000+int64(curTime.Nanosecond()/1e6)) + offset
|
||||
}
|
||||
|
||||
// timeStampOffset returns a randomized timestamp offset to be used when sending
|
||||
@@ -2843,6 +2842,14 @@ func (e *endpoint) completeState() stack.TCPEndpointState {
|
||||
WEst: cubic.wEst,
|
||||
}
|
||||
}
|
||||
|
||||
rc := e.snd.rc
|
||||
s.Sender.RACKState = stack.TCPRACKState{
|
||||
XmitTime: rc.xmitTime,
|
||||
EndSequence: rc.endSequence,
|
||||
FACK: rc.fack,
|
||||
RTT: rc.rtt,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
||||
)
|
||||
|
||||
// RACK is a loss detection algorithm used in TCP to detect packet loss and
|
||||
// reordering using transmission timestamp of the packets instead of packet or
|
||||
// sequence counts. To use RACK, SACK should be enabled on the connection.
|
||||
|
||||
// rackControl stores the rack related fields.
|
||||
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-6.1
|
||||
//
|
||||
// +stateify savable
|
||||
type rackControl struct {
|
||||
// xmitTime is the latest transmission timestamp of rackControl.seg.
|
||||
xmitTime time.Time `state:".(unixTime)"`
|
||||
|
||||
// endSequence is the ending TCP sequence number of rackControl.seg.
|
||||
endSequence seqnum.Value
|
||||
|
||||
// fack is the highest selectively or cumulatively acknowledged
|
||||
// sequence.
|
||||
fack seqnum.Value
|
||||
|
||||
// rtt is the RTT of the most recently delivered packet on the
|
||||
// connection (either cumulatively acknowledged or selectively
|
||||
// acknowledged) that was not marked invalid as a possible spurious
|
||||
// retransmission.
|
||||
rtt time.Duration
|
||||
}
|
||||
|
||||
// Update will update the RACK related fields when an ACK has been received.
|
||||
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
|
||||
func (rc *rackControl) Update(seg *segment, ackSeg *segment, srtt time.Duration, offset uint32) {
|
||||
rtt := time.Now().Sub(seg.xmitTime)
|
||||
|
||||
// If the ACK is for a retransmitted packet, do not update if it is a
|
||||
// spurious inference which is determined by below checks:
|
||||
// 1. When Timestamping option is available, if the TSVal is less than the
|
||||
// transmit time of the most recent retransmitted packet.
|
||||
// 2. When RTT calculated for the packet is less than the smoothed RTT
|
||||
// for the connection.
|
||||
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.2
|
||||
// step 2
|
||||
if seg.xmitCount > 1 {
|
||||
if ackSeg.parsedOptions.TS && ackSeg.parsedOptions.TSEcr != 0 {
|
||||
if ackSeg.parsedOptions.TSEcr < tcpTimeStamp(seg.xmitTime, offset) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if rtt < srtt {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rc.rtt = rtt
|
||||
// Update rc.xmitTime and rc.endSequence to the transmit time and
|
||||
// ending sequence number of the packet which has been acknowledged
|
||||
// most recently.
|
||||
endSeq := seg.sequenceNumber.Add(seqnum.Size(seg.data.Size()))
|
||||
if rc.xmitTime.Before(seg.xmitTime) || (seg.xmitTime.Equal(rc.xmitTime) && rc.endSequence.LessThan(endSeq)) {
|
||||
rc.xmitTime = seg.xmitTime
|
||||
rc.endSequence = endSeq
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// saveXmitTime is invoked by stateify.
|
||||
func (rc *rackControl) saveXmitTime() unixTime {
|
||||
return unixTime{rc.xmitTime.Unix(), rc.xmitTime.UnixNano()}
|
||||
}
|
||||
|
||||
// loadXmitTime is invoked by stateify.
|
||||
func (rc *rackControl) loadXmitTime(unix unixTime) {
|
||||
rc.xmitTime = time.Unix(unix.second, unix.nano)
|
||||
}
|
||||
@@ -191,6 +191,10 @@ type sender struct {
|
||||
|
||||
// cc is the congestion control algorithm in use for this sender.
|
||||
cc congestionControl
|
||||
|
||||
// rc has the fields needed for implementing RACK loss detection
|
||||
// algorithm.
|
||||
rc rackControl
|
||||
}
|
||||
|
||||
// rtt is a synchronization wrapper used to appease stateify. See the comment
|
||||
@@ -1272,21 +1276,21 @@ func (s *sender) checkDuplicateAck(seg *segment) (rtx bool) {
|
||||
|
||||
// handleRcvdSegment is called when a segment is received; it is responsible for
|
||||
// updating the send-related state.
|
||||
func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
func (s *sender) handleRcvdSegment(rcvdSeg *segment) {
|
||||
// Check if we can extract an RTT measurement from this ack.
|
||||
if !seg.parsedOptions.TS && s.rttMeasureSeqNum.LessThan(seg.ackNumber) {
|
||||
if !rcvdSeg.parsedOptions.TS && s.rttMeasureSeqNum.LessThan(rcvdSeg.ackNumber) {
|
||||
s.updateRTO(time.Now().Sub(s.rttMeasureTime))
|
||||
s.rttMeasureSeqNum = s.sndNxt
|
||||
}
|
||||
|
||||
// Update Timestamp if required. See RFC7323, section-4.3.
|
||||
if s.ep.sendTSOk && seg.parsedOptions.TS {
|
||||
s.ep.updateRecentTimestamp(seg.parsedOptions.TSVal, s.maxSentAck, seg.sequenceNumber)
|
||||
if s.ep.sendTSOk && rcvdSeg.parsedOptions.TS {
|
||||
s.ep.updateRecentTimestamp(rcvdSeg.parsedOptions.TSVal, s.maxSentAck, rcvdSeg.sequenceNumber)
|
||||
}
|
||||
|
||||
// Insert SACKBlock information into our scoreboard.
|
||||
if s.ep.sackPermitted {
|
||||
for _, sb := range seg.parsedOptions.SACKBlocks {
|
||||
for _, sb := range rcvdSeg.parsedOptions.SACKBlocks {
|
||||
// Only insert the SACK block if the following holds
|
||||
// true:
|
||||
// * SACK block acks data after the ack number in the
|
||||
@@ -1299,27 +1303,27 @@ func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
// NOTE: This check specifically excludes DSACK blocks
|
||||
// which have start/end before sndUna and are used to
|
||||
// indicate spurious retransmissions.
|
||||
if seg.ackNumber.LessThan(sb.Start) && s.sndUna.LessThan(sb.Start) && sb.End.LessThanEq(s.sndNxt) && !s.ep.scoreboard.IsSACKED(sb) {
|
||||
if rcvdSeg.ackNumber.LessThan(sb.Start) && s.sndUna.LessThan(sb.Start) && sb.End.LessThanEq(s.sndNxt) && !s.ep.scoreboard.IsSACKED(sb) {
|
||||
s.ep.scoreboard.Insert(sb)
|
||||
seg.hasNewSACKInfo = true
|
||||
rcvdSeg.hasNewSACKInfo = true
|
||||
}
|
||||
}
|
||||
s.SetPipe()
|
||||
}
|
||||
|
||||
// Count the duplicates and do the fast retransmit if needed.
|
||||
rtx := s.checkDuplicateAck(seg)
|
||||
rtx := s.checkDuplicateAck(rcvdSeg)
|
||||
|
||||
// Stash away the current window size.
|
||||
s.sndWnd = seg.window
|
||||
s.sndWnd = rcvdSeg.window
|
||||
|
||||
ack := seg.ackNumber
|
||||
ack := rcvdSeg.ackNumber
|
||||
|
||||
// Disable zero window probing if remote advertizes a non-zero receive
|
||||
// window. This can be with an ACK to the zero window probe (where the
|
||||
// acknumber refers to the already acknowledged byte) OR to any previously
|
||||
// unacknowledged segment.
|
||||
if s.zeroWindowProbing && seg.window > 0 &&
|
||||
if s.zeroWindowProbing && rcvdSeg.window > 0 &&
|
||||
(ack == s.sndUna || (ack-1).InRange(s.sndUna, s.sndNxt)) {
|
||||
s.disableZeroWindowProbing()
|
||||
}
|
||||
@@ -1344,10 +1348,10 @@ func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
// averaged RTT measurement only if the segment acknowledges
|
||||
// some new data, i.e., only if it advances the left edge of
|
||||
// the send window.
|
||||
if s.ep.sendTSOk && seg.parsedOptions.TSEcr != 0 {
|
||||
if s.ep.sendTSOk && rcvdSeg.parsedOptions.TSEcr != 0 {
|
||||
// TSVal/Ecr values sent by Netstack are at a millisecond
|
||||
// granularity.
|
||||
elapsed := time.Duration(s.ep.timestamp()-seg.parsedOptions.TSEcr) * time.Millisecond
|
||||
elapsed := time.Duration(s.ep.timestamp()-rcvdSeg.parsedOptions.TSEcr) * time.Millisecond
|
||||
s.updateRTO(elapsed)
|
||||
}
|
||||
|
||||
@@ -1361,6 +1365,9 @@ func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
|
||||
ackLeft := acked
|
||||
originalOutstanding := s.outstanding
|
||||
s.rtt.Lock()
|
||||
srtt := s.rtt.srtt
|
||||
s.rtt.Unlock()
|
||||
for ackLeft > 0 {
|
||||
// We use logicalLen here because we can have FIN
|
||||
// segments (which are always at the end of list) that
|
||||
@@ -1380,6 +1387,11 @@ func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
s.writeNext = seg.Next()
|
||||
}
|
||||
|
||||
// Update the RACK fields if SACK is enabled.
|
||||
if s.ep.sackPermitted {
|
||||
s.rc.Update(seg, rcvdSeg, srtt, s.ep.tsOffset)
|
||||
}
|
||||
|
||||
s.writeList.Remove(seg)
|
||||
|
||||
// if SACK is enabled then Only reduce outstanding if
|
||||
@@ -1435,7 +1447,7 @@ func (s *sender) handleRcvdSegment(seg *segment) {
|
||||
// that the window opened up, or the congestion window was inflated due
|
||||
// to a duplicate ack during fast recovery. This will also re-enable
|
||||
// the retransmit timer if needed.
|
||||
if !s.ep.sackPermitted || s.fr.active || s.dupAckCount == 0 || seg.hasNewSACKInfo {
|
||||
if !s.ep.sackPermitted || s.fr.active || s.dupAckCount == 0 || rcvdSeg.hasNewSACKInfo {
|
||||
s.sendData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tcp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
|
||||
)
|
||||
|
||||
// TestRACKUpdate tests the RACK related fields are updated when an ACK is
|
||||
// received on a SACK enabled connection.
|
||||
func TestRACKUpdate(t *testing.T) {
|
||||
const maxPayload = 10
|
||||
const tsOptionSize = 12
|
||||
const maxTCPOptionSize = 40
|
||||
|
||||
c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
|
||||
defer c.Cleanup()
|
||||
|
||||
var xmitTime time.Time
|
||||
c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
|
||||
// Validate that the endpoint Sender.RACKState is what we expect.
|
||||
if state.Sender.RACKState.XmitTime.Before(xmitTime) {
|
||||
t.Fatalf("RACK transmit time failed to update when an ACK is received")
|
||||
}
|
||||
|
||||
gotSeq := state.Sender.RACKState.EndSequence
|
||||
wantSeq := state.Sender.SndNxt
|
||||
if !gotSeq.LessThanEq(wantSeq) || gotSeq.LessThan(wantSeq) {
|
||||
t.Fatalf("RACK sequence number failed to update, got: %v, but want: %v", gotSeq, wantSeq)
|
||||
}
|
||||
|
||||
if state.Sender.RACKState.RTT == 0 {
|
||||
t.Fatalf("RACK RTT failed to update when an ACK is received")
|
||||
}
|
||||
})
|
||||
setStackSACKPermitted(t, c, true)
|
||||
createConnectedWithSACKAndTS(c)
|
||||
|
||||
data := buffer.NewView(maxPayload)
|
||||
for i := range data {
|
||||
data[i] = byte(i)
|
||||
}
|
||||
|
||||
// Write the data.
|
||||
xmitTime = time.Now()
|
||||
if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
|
||||
t.Fatalf("Write failed: %s", err)
|
||||
}
|
||||
|
||||
bytesRead := 0
|
||||
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
|
||||
bytesRead += maxPayload
|
||||
c.SendAck(790, bytesRead)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
Reference in New Issue
Block a user