mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement support for SACK based recovery(RFC 6675).
PiperOrigin-RevId: 246536003 Change-Id: I118b745f45040be9c70cb6a1028acdb06c78d8c9
This commit is contained in:
committed by
Shentubot
parent
95614bbefa
commit
458fe955a7
@@ -102,6 +102,18 @@ type TCPFastRecoveryState struct {
|
||||
// MaxCwnd is the maximum value we are permitted to grow the congestion
|
||||
// window during recovery. This is set at the time we enter recovery.
|
||||
MaxCwnd int
|
||||
|
||||
// HighRxt is the highest sequence number which has been retransmitted
|
||||
// during the current loss recovery phase.
|
||||
// See: RFC 6675 Section 2 for details.
|
||||
HighRxt seqnum.Value
|
||||
|
||||
// RescueRxt is the highest sequence number which has been
|
||||
// optimistically retransmitted to prevent stalling of the ACK clock
|
||||
// when there is loss at the end of the window and no new data is
|
||||
// available for transmission.
|
||||
// See: RFC 6675 Section 2 for details.
|
||||
RescueRxt seqnum.Value
|
||||
}
|
||||
|
||||
// TCPReceiverState holds a copy of the internal state of the receiver for
|
||||
@@ -1024,7 +1036,7 @@ func (s *Stack) TransportProtocolInstance(num tcpip.TransportProtocolNumber) Tra
|
||||
|
||||
// AddTCPProbe installs a probe function that will be invoked on every segment
|
||||
// received by a given TCP endpoint. The probe function is passed a copy of the
|
||||
// TCP endpoint state.
|
||||
// TCP endpoint state before and after processing of the segment.
|
||||
//
|
||||
// NOTE: TCPProbe is added only to endpoints created after this call. Endpoints
|
||||
// created prior to this call will not call the probe function.
|
||||
|
||||
@@ -790,7 +790,7 @@ func (e *endpoint) keepaliveTimerExpired() *tcpip.Error {
|
||||
// seg.seq = snd.nxt-1.
|
||||
e.keepalive.unacked++
|
||||
e.keepalive.Unlock()
|
||||
e.snd.sendSegment(buffer.VectorisedView{}, header.TCPFlagAck, e.snd.sndNxt-1)
|
||||
e.snd.sendSegmentFromView(buffer.VectorisedView{}, header.TCPFlagAck, e.snd.sndNxt-1)
|
||||
e.resetKeepaliveTimer(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1673,10 +1673,12 @@ func (e *endpoint) completeState() stack.TCPEndpointState {
|
||||
LastSendTime: e.snd.lastSendTime,
|
||||
DupAckCount: e.snd.dupAckCount,
|
||||
FastRecovery: stack.TCPFastRecoveryState{
|
||||
Active: e.snd.fr.active,
|
||||
First: e.snd.fr.first,
|
||||
Last: e.snd.fr.last,
|
||||
MaxCwnd: e.snd.fr.maxCwnd,
|
||||
Active: e.snd.fr.active,
|
||||
First: e.snd.fr.first,
|
||||
Last: e.snd.fr.last,
|
||||
MaxCwnd: e.snd.fr.maxCwnd,
|
||||
HighRxt: e.snd.fr.highRxt,
|
||||
RescueRxt: e.snd.fr.rescueRxt,
|
||||
},
|
||||
SndCwnd: e.snd.sndCwnd,
|
||||
Ssthresh: e.snd.sndSsthresh,
|
||||
|
||||
@@ -38,6 +38,13 @@ const (
|
||||
//
|
||||
// +stateify savable
|
||||
type SACKScoreboard struct {
|
||||
// smss is defined in RFC5681 as following:
|
||||
//
|
||||
// The SMSS is the size of the largest segment that the sender can
|
||||
// transmit. This value can be based on the maximum transmission unit
|
||||
// of the network, the path MTU discovery [RFC1191, RFC4821] algorithm,
|
||||
// RMSS (see next item), or other factors. The size does not include
|
||||
// the TCP/IP headers and options.
|
||||
smss uint16
|
||||
maxSACKED seqnum.Value
|
||||
sacked seqnum.Size `state:"nosave"`
|
||||
@@ -138,6 +145,10 @@ func (s *SACKScoreboard) Insert(r header.SACKBlock) {
|
||||
// IsSACKED returns true if the a given range of sequence numbers denoted by r
|
||||
// are already covered by SACK information in the scoreboard.
|
||||
func (s *SACKScoreboard) IsSACKED(r header.SACKBlock) bool {
|
||||
if s.Empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
found := false
|
||||
s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool {
|
||||
sacked := i.(header.SACKBlock)
|
||||
@@ -205,17 +216,46 @@ func (s *SACKScoreboard) Copy() (sackBlocks []header.SACKBlock, maxSACKED seqnum
|
||||
return sackBlocks, s.maxSACKED
|
||||
}
|
||||
|
||||
// IsLost implements the IsLost(SeqNum) operation defined in RFC 3517 section 4.
|
||||
//
|
||||
// This routine returns whether the given sequence number is considered to be
|
||||
// lost. The routine returns true when either nDupAckThreshold discontiguous
|
||||
// SACKed sequences have arrived above 'SeqNum' or (nDupAckThreshold * SMSS)
|
||||
// bytes with sequence numbers greater than 'SeqNum' have been SACKed.
|
||||
// Otherwise, the routine returns false.
|
||||
func (s *SACKScoreboard) IsLost(r header.SACKBlock) bool {
|
||||
// IsRangeLost implements the IsLost(SeqNum) operation defined in RFC 6675
|
||||
// section 4 but operates on a range of sequence numbers and returns true if
|
||||
// there are at least nDupAckThreshold SACK blocks greater than the range being
|
||||
// checked or if at least (nDupAckThreshold-1)*s.smss bytes have been SACKED
|
||||
// with sequence numbers greater than the block being checked.
|
||||
func (s *SACKScoreboard) IsRangeLost(r header.SACKBlock) bool {
|
||||
if s.Empty() {
|
||||
return false
|
||||
}
|
||||
nDupSACK := 0
|
||||
nDupSACKBytes := seqnum.Size(0)
|
||||
isLost := false
|
||||
|
||||
// We need to check if the immediate lower (if any) sacked
|
||||
// range contains or partially overlaps with r.
|
||||
searchMore := true
|
||||
s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool {
|
||||
sacked := i.(header.SACKBlock)
|
||||
if sacked.Contains(r) {
|
||||
searchMore = false
|
||||
return false
|
||||
}
|
||||
if sacked.End.LessThanEq(r.Start) {
|
||||
// all sequence numbers covered by sacked are below
|
||||
// r so we continue searching.
|
||||
return false
|
||||
}
|
||||
// There is a partial overlap. In this case we r.Start is
|
||||
// between sacked.Start & sacked.End and r.End extends beyond
|
||||
// sacked.End.
|
||||
// Move r.Start to sacked.End and continuing searching blocks
|
||||
// above r.Start.
|
||||
r.Start = sacked.End
|
||||
return false
|
||||
})
|
||||
|
||||
if !searchMore {
|
||||
return isLost
|
||||
}
|
||||
|
||||
s.ranges.AscendGreaterOrEqual(r, func(i btree.Item) bool {
|
||||
sacked := i.(header.SACKBlock)
|
||||
if sacked.Contains(r) {
|
||||
@@ -232,6 +272,18 @@ func (s *SACKScoreboard) IsLost(r header.SACKBlock) bool {
|
||||
return isLost
|
||||
}
|
||||
|
||||
// IsLost implements the IsLost(SeqNum) operation defined in RFC3517 section
|
||||
// 4.
|
||||
//
|
||||
// This routine returns whether the given sequence number is considered to be
|
||||
// lost. The routine returns true when either nDupAckThreshold discontiguous
|
||||
// SACKed sequences have arrived above 'SeqNum' or (nDupAckThreshold * SMSS)
|
||||
// bytes with sequence numbers greater than 'SeqNum' have been SACKed.
|
||||
// Otherwise, the routine returns false.
|
||||
func (s *SACKScoreboard) IsLost(seq seqnum.Value) bool {
|
||||
return s.IsRangeLost(header.SACKBlock{seq, seq.Add(1)})
|
||||
}
|
||||
|
||||
// Empty returns true if the SACK scoreboard has no entries, false otherwise.
|
||||
func (s *SACKScoreboard) Empty() bool {
|
||||
return s.ranges.Len() == 0
|
||||
@@ -247,3 +299,8 @@ func (s *SACKScoreboard) Sacked() seqnum.Size {
|
||||
func (s *SACKScoreboard) MaxSACKED() seqnum.Value {
|
||||
return s.maxSACKED
|
||||
}
|
||||
|
||||
// SMSS returns the sender's MSS as held by the SACK scoreboard.
|
||||
func (s *SACKScoreboard) SMSS() uint16 {
|
||||
return s.smss
|
||||
}
|
||||
|
||||
@@ -97,31 +97,120 @@ func TestSACKScoreboardIsSACKED(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSACKScoreboardIsLost(t *testing.T) {
|
||||
func TestSACKScoreboardIsRangeLost(t *testing.T) {
|
||||
s := tcp.NewSACKScoreboard(10, 0)
|
||||
s.Insert(header.SACKBlock{1, 50})
|
||||
s.Insert(header.SACKBlock{1, 25})
|
||||
s.Insert(header.SACKBlock{25, 50})
|
||||
s.Insert(header.SACKBlock{51, 100})
|
||||
s.Insert(header.SACKBlock{111, 120})
|
||||
s.Insert(header.SACKBlock{101, 110})
|
||||
s.Insert(header.SACKBlock{121, 141})
|
||||
s.Insert(header.SACKBlock{145, 146})
|
||||
s.Insert(header.SACKBlock{147, 148})
|
||||
s.Insert(header.SACKBlock{149, 150})
|
||||
s.Insert(header.SACKBlock{153, 154})
|
||||
s.Insert(header.SACKBlock{155, 156})
|
||||
testCases := []struct {
|
||||
block header.SACKBlock
|
||||
lost bool
|
||||
}{
|
||||
// Block not covered by SACK block and has more than
|
||||
// nDupAckThreshold discontiguous SACK blocks after it as well
|
||||
// as (nDupAckThreshold -1) * 10 (smss) bytes that have been
|
||||
// SACKED above the sequence number covered by this block.
|
||||
{block: header.SACKBlock{0, 1}, lost: true},
|
||||
|
||||
// These blocks have all been SACKed and should not be
|
||||
// considered lost.
|
||||
{block: header.SACKBlock{1, 2}, lost: false},
|
||||
{block: header.SACKBlock{25, 26}, lost: false},
|
||||
{block: header.SACKBlock{1, 45}, lost: false},
|
||||
|
||||
// Same as the first case above.
|
||||
{block: header.SACKBlock{50, 51}, lost: true},
|
||||
// This one should return true because there are
|
||||
// > (nDupAckThreshold - 1) * 10 (smss) bytes that have been sacked above
|
||||
// this sequence number.
|
||||
{block: header.SACKBlock{119, 120}, lost: true},
|
||||
|
||||
// This block has been SACKed and should not be considered lost.
|
||||
{block: header.SACKBlock{119, 120}, lost: false},
|
||||
|
||||
// This one should return true because there are >
|
||||
// (nDupAckThreshold - 1) * 10 (smss) bytes that have been
|
||||
// sacked above this sequence number.
|
||||
{block: header.SACKBlock{120, 121}, lost: true},
|
||||
|
||||
// This block has been SACKed and should not be considered lost.
|
||||
{block: header.SACKBlock{125, 126}, lost: false},
|
||||
|
||||
// This block has not been SACKed and there are nDupAckThreshold
|
||||
// number of SACKed blocks after it.
|
||||
{block: header.SACKBlock{141, 145}, lost: true},
|
||||
|
||||
// This block has not been SACKed and there are less than
|
||||
// nDupAckThreshold SACKed sequences after it.
|
||||
{block: header.SACKBlock{151, 152}, lost: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
if want, got := tc.lost, s.IsLost(tc.block); got != want {
|
||||
t.Errorf("s.IsLost(%v) = %v, want %v", tc.block, got, want)
|
||||
if want, got := tc.lost, s.IsRangeLost(tc.block); got != want {
|
||||
t.Errorf("s.IsRangeLost(%v) = %v, want %v", tc.block, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSACKScoreboardIsLost(t *testing.T) {
|
||||
s := tcp.NewSACKScoreboard(10, 0)
|
||||
s.Insert(header.SACKBlock{1, 25})
|
||||
s.Insert(header.SACKBlock{25, 50})
|
||||
s.Insert(header.SACKBlock{51, 100})
|
||||
s.Insert(header.SACKBlock{111, 120})
|
||||
s.Insert(header.SACKBlock{101, 110})
|
||||
s.Insert(header.SACKBlock{121, 141})
|
||||
s.Insert(header.SACKBlock{121, 141})
|
||||
s.Insert(header.SACKBlock{145, 146})
|
||||
s.Insert(header.SACKBlock{147, 148})
|
||||
s.Insert(header.SACKBlock{149, 150})
|
||||
s.Insert(header.SACKBlock{153, 154})
|
||||
s.Insert(header.SACKBlock{155, 156})
|
||||
testCases := []struct {
|
||||
seq seqnum.Value
|
||||
lost bool
|
||||
}{
|
||||
// Sequence number not covered by SACK block and has more than
|
||||
// nDupAckThreshold discontiguous SACK blocks after it as well
|
||||
// as (nDupAckThreshold -1) * 10 (smss) bytes that have been
|
||||
// SACKED above the sequence number.
|
||||
{seq: 0, lost: true},
|
||||
|
||||
// These sequence numbers have all been SACKed and should not be
|
||||
// considered lost.
|
||||
{seq: 1, lost: false},
|
||||
{seq: 25, lost: false},
|
||||
{seq: 45, lost: false},
|
||||
|
||||
// Same as first case above.
|
||||
{seq: 50, lost: true},
|
||||
|
||||
// This block has been SACKed and should not be considered lost.
|
||||
{seq: 119, lost: false},
|
||||
|
||||
// This one should return true because there are >
|
||||
// (nDupAckThreshold - 1) * 10 (smss) bytes that have been
|
||||
// sacked above this sequence number.
|
||||
{seq: 120, lost: true},
|
||||
|
||||
// This sequence number has been SACKed and should not be
|
||||
// considered lost.
|
||||
{seq: 125, lost: false},
|
||||
|
||||
// This sequence number has not been SACKed and there are
|
||||
// nDupAckThreshold number of SACKed blocks after it.
|
||||
{seq: 141, lost: true},
|
||||
|
||||
// This sequence number has not been SACKed and there are less
|
||||
// than nDupAckThreshold SACKed sequences after it.
|
||||
{seq: 151, lost: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
if want, got := tc.lost, s.IsLost(tc.seq); got != want {
|
||||
t.Errorf("s.IsLost(%v) = %v, want %v", tc.seq, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,3 +179,8 @@ func (s *segment) parse() bool {
|
||||
s.window = seqnum.Size(h.WindowSize())
|
||||
return true
|
||||
}
|
||||
|
||||
// sackBlock returns a header.SACKBlock that represents this segment.
|
||||
func (s *segment) sackBlock() header.SACKBlock {
|
||||
return header.SACKBlock{s.sequenceNumber, s.sequenceNumber.Add(s.logicalLen())}
|
||||
}
|
||||
|
||||
+530
-197
File diff suppressed because it is too large
Load Diff
@@ -16,22 +16,33 @@ package tcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/header"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/seqnum"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.googlesource.com/gvisor/pkg/tcpip/transport/tcp/testing/context"
|
||||
)
|
||||
|
||||
// createConnectWithSACKPermittedOption creates and connects c.ep with the
|
||||
// createConnectedWithSACKPermittedOption creates and connects c.ep with the
|
||||
// SACKPermitted option enabled if the stack in the context has the SACK support
|
||||
// enabled.
|
||||
func createConnectedWithSACKPermittedOption(c *context.Context) *context.RawEndpoint {
|
||||
return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled()})
|
||||
}
|
||||
|
||||
// createConnectedWithSACKAndTS creates and connects c.ep with the SACK & TS
|
||||
// option enabled if the stack in the context has SACK and TS enabled.
|
||||
func createConnectedWithSACKAndTS(c *context.Context) *context.RawEndpoint {
|
||||
return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled(), TS: true})
|
||||
}
|
||||
|
||||
func setStackSACKPermitted(t *testing.T, c *context.Context, enable bool) {
|
||||
t.Helper()
|
||||
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SACKEnabled(enable)); err != nil {
|
||||
@@ -348,3 +359,206 @@ func TestTrimSackBlockList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSACKRecovery(t *testing.T) {
|
||||
const maxPayload = 10
|
||||
// See: tcp.makeOptions for why tsOptionSize is set to 12 here.
|
||||
const tsOptionSize = 12
|
||||
// Enabling SACK means the payload size is reduced to account
|
||||
// for the extra space required for the TCP options.
|
||||
//
|
||||
// We increase the MTU by 40 bytes to account for SACK and Timestamp
|
||||
// options.
|
||||
const maxTCPOptionSize = 40
|
||||
|
||||
c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
|
||||
defer c.Cleanup()
|
||||
|
||||
c.Stack().AddTCPProbe(func(s stack.TCPEndpointState) {
|
||||
// We use log.Printf instead of t.Logf here because this probe
|
||||
// can fire even when the test function has finished. This is
|
||||
// because closing the endpoint in cleanup() does not mean the
|
||||
// actual worker loop terminates immediately as it still has to
|
||||
// do a full TCP shutdown. But this test can finish running
|
||||
// before the shutdown is done. Using t.Logf in such a case
|
||||
// causes the test to panic due to logging after test finished.
|
||||
log.Printf("state: %+v\n", s)
|
||||
})
|
||||
setStackSACKPermitted(t, c, true)
|
||||
createConnectedWithSACKAndTS(c)
|
||||
|
||||
const iterations = 7
|
||||
data := buffer.NewView(2 * maxPayload * (tcp.InitialCwnd << (iterations + 1)))
|
||||
for i := range data {
|
||||
data[i] = byte(i)
|
||||
}
|
||||
|
||||
// Write all the data in one shot. Packets will only be written at the
|
||||
// MTU size though.
|
||||
if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
|
||||
// Do slow start for a few iterations.
|
||||
expected := tcp.InitialCwnd
|
||||
bytesRead := 0
|
||||
for i := 0; i < iterations; i++ {
|
||||
expected = tcp.InitialCwnd << uint(i)
|
||||
if i > 0 {
|
||||
// Acknowledge all the data received so far if not on
|
||||
// first iteration.
|
||||
c.SendAck(790, bytesRead)
|
||||
}
|
||||
|
||||
// Read all packets expected on this iteration. Don't
|
||||
// acknowledge any of them just yet, so that we can measure the
|
||||
// congestion window.
|
||||
for j := 0; j < expected; j++ {
|
||||
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
|
||||
bytesRead += maxPayload
|
||||
}
|
||||
|
||||
// Check we don't receive any more packets on this iteration.
|
||||
// The timeout can't be too high or we'll trigger a timeout.
|
||||
c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
|
||||
}
|
||||
|
||||
// Send 3 duplicate acks. This should force an immediate retransmit of
|
||||
// the pending packet and put the sender into fast recovery.
|
||||
rtxOffset := bytesRead - maxPayload*expected
|
||||
start := c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
|
||||
end := start.Add(10)
|
||||
for i := 0; i < 3; i++ {
|
||||
c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
|
||||
end = end.Add(10)
|
||||
}
|
||||
|
||||
// Receive the retransmitted packet.
|
||||
c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
|
||||
|
||||
tcpStats := c.Stack().Stats().TCP
|
||||
stats := []struct {
|
||||
stat *tcpip.StatCounter
|
||||
name string
|
||||
want uint64
|
||||
}{
|
||||
{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
|
||||
{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
|
||||
{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
|
||||
{tcpStats.FastRecovery, "stats.TCP.FastRecovery", 0},
|
||||
}
|
||||
for _, s := range stats {
|
||||
if got, want := s.stat.Value(), s.want; got != want {
|
||||
t.Errorf("got %s.Value() = %v, want = %v", s.name, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Now send 7 mode duplicate ACKs. In SACK TCP dupAcks do not cause
|
||||
// window inflation and sending of packets is completely handled by the
|
||||
// SACK Recovery algorithm. We should see no packets being released, as
|
||||
// the cwnd at this point after entering recovery should be half of the
|
||||
// outstanding number of packets in flight.
|
||||
for i := 0; i < 7; i++ {
|
||||
c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
|
||||
end = end.Add(10)
|
||||
}
|
||||
|
||||
recover := bytesRead
|
||||
|
||||
// Ensure no new packets arrive.
|
||||
c.CheckNoPacketTimeout("More packets received than expected during recovery after dupacks for this cwnd.",
|
||||
50*time.Millisecond)
|
||||
|
||||
// Acknowledge half of the pending data. This along with the 10 sacked
|
||||
// segments above should reduce the outstanding below the current
|
||||
// congestion window allowing the sender to transmit data.
|
||||
rtxOffset = bytesRead - expected*maxPayload/2
|
||||
|
||||
// Now send a partial ACK w/ a SACK block that indicates that the next 3
|
||||
// segments are lost and we have received 6 segments after the lost
|
||||
// segments. This should cause the sender to immediately transmit all 3
|
||||
// segments in response to this ACK unlike in FastRecovery where only 1
|
||||
// segment is retransmitted per ACK.
|
||||
start = c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
|
||||
end = start.Add(60)
|
||||
c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
|
||||
|
||||
// At this point, we acked expected/2 packets and we SACKED 6 packets and
|
||||
// 3 segments were considered lost due to the SACK block we sent.
|
||||
//
|
||||
// So total packets outstanding can be calculated as follows after 7
|
||||
// iterations of slow start -> 10/20/40/80/160/320/640. So expected
|
||||
// should be 640 at start, then we went to recover at which point the
|
||||
// cwnd should be set to 320 + 3 (for the 3 dupAcks which have left the
|
||||
// network).
|
||||
// Outstanding at this point after acking half the window
|
||||
// (320 packets) will be:
|
||||
// outstanding = 640-320-6(due to SACK block)-3 = 311
|
||||
//
|
||||
// The last 3 is due to the fact that the first 3 packets after
|
||||
// rtxOffset will be considered lost due to the SACK blocks sent.
|
||||
// Receive the retransmit due to partial ack.
|
||||
|
||||
c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
|
||||
// Receive the 2 extra packets that should have been retransmitted as
|
||||
// those should be considered lost and immediately retransmitted based
|
||||
// on the SACK information in the previous ACK sent above.
|
||||
for i := 0; i < 2; i++ {
|
||||
c.ReceiveAndCheckPacketWithOptions(data, rtxOffset+maxPayload*(i+1), maxPayload, tsOptionSize)
|
||||
}
|
||||
|
||||
// Now we should get 9 more new unsent packets as the cwnd is 323 and
|
||||
// outstanding is 311.
|
||||
for i := 0; i < 9; i++ {
|
||||
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
|
||||
bytesRead += maxPayload
|
||||
}
|
||||
|
||||
// In SACK recovery only the first segment is fast retransmitted when
|
||||
// entering recovery.
|
||||
if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(1); got != want {
|
||||
t.Errorf("got stats.TCP.FastRetransmit.Value = %v, want = %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(4); got != want {
|
||||
t.Errorf("got stats.TCP.Retransmits.Value = %v, want = %v", got, want)
|
||||
}
|
||||
|
||||
c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.", 50*time.Millisecond)
|
||||
|
||||
// Acknowledge all pending data to recover point.
|
||||
c.SendAck(790, recover)
|
||||
|
||||
// At this point, the cwnd should reset to expected/2 and there are 9
|
||||
// packets outstanding.
|
||||
//
|
||||
// Now in the first iteration since there are 9 packets outstanding.
|
||||
// We would expect to get expected/2 - 9 packets. But subsequent
|
||||
// iterations will send us expected/2 + 1 (per iteration).
|
||||
expected = expected/2 - 9
|
||||
for i := 0; i < iterations; i++ {
|
||||
// Read all packets expected on this iteration. Don't
|
||||
// acknowledge any of them just yet, so that we can measure the
|
||||
// congestion window.
|
||||
for j := 0; j < expected; j++ {
|
||||
c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
|
||||
bytesRead += maxPayload
|
||||
}
|
||||
// Check we don't receive any more packets on this iteration.
|
||||
// The timeout can't be too high or we'll trigger a timeout.
|
||||
c.CheckNoPacketTimeout(fmt.Sprintf("More packets received(after deflation) than expected %d for this cwnd and iteration: %d.", expected, i), 50*time.Millisecond)
|
||||
|
||||
// Acknowledge all the data received so far.
|
||||
c.SendAck(790, bytesRead)
|
||||
|
||||
// In cogestion avoidance, the packets trains increase by 1 in
|
||||
// each iteration.
|
||||
if i == 0 {
|
||||
// After the first iteration we expect to get the full
|
||||
// congestion window worth of packets in every
|
||||
// iteration.
|
||||
expected += 9
|
||||
}
|
||||
expected++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1792,12 +1792,12 @@ func TestSynOptionsOnActiveConnect(t *testing.T) {
|
||||
|
||||
// Receive SYN packet.
|
||||
b := c.GetPacket()
|
||||
|
||||
mss := uint16(mtu - header.IPv4MinimumSize - header.TCPMinimumSize)
|
||||
checker.IPv4(t, b,
|
||||
checker.TCP(
|
||||
checker.DstPort(context.TestPort),
|
||||
checker.TCPFlags(header.TCPFlagSyn),
|
||||
checker.TCPSynOptions(header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize, WS: wndScale}),
|
||||
checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1812,7 +1812,7 @@ func TestSynOptionsOnActiveConnect(t *testing.T) {
|
||||
checker.TCPFlags(header.TCPFlagSyn),
|
||||
checker.SrcPort(tcp.SourcePort()),
|
||||
checker.SeqNum(tcp.SequenceNumber()),
|
||||
checker.TCPSynOptions(header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize, WS: wndScale}),
|
||||
checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2737,7 +2737,8 @@ func TestFastRecovery(t *testing.T) {
|
||||
// A partial ACK during recovery should reduce congestion window by the
|
||||
// number acked. Since we had "expected" packets outstanding before sending
|
||||
// partial ack and we acked expected/2 , the cwnd and outstanding should
|
||||
// be expected/2 + 7. Which means the sender should not send any more packets
|
||||
// be expected/2 + 10 (7 dupAcks + 3 for the original 3 dupacks that triggered
|
||||
// fast recovery). Which means the sender should not send any more packets
|
||||
// till we ack this one.
|
||||
c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.",
|
||||
50*time.Millisecond)
|
||||
@@ -2843,7 +2844,7 @@ func TestRetransmit(t *testing.T) {
|
||||
}
|
||||
|
||||
if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(1); got != want {
|
||||
t.Errorf("got stats.TCP.Retransmit.Value = %v, want = %v", got, want)
|
||||
t.Errorf("got stats.TCP.Retransmits.Value = %v, want = %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := c.Stack().Stats().TCP.SlowStartRetransmits.Value(), uint64(1); got != want {
|
||||
|
||||
@@ -355,13 +355,27 @@ func (c *Context) SendPacket(payload []byte, h *Headers) {
|
||||
|
||||
// SendAck sends an ACK packet.
|
||||
func (c *Context) SendAck(seq seqnum.Value, bytesReceived int) {
|
||||
c.SendAckWithSACK(seq, bytesReceived, nil)
|
||||
}
|
||||
|
||||
// SendAckWithSACK sends an ACK packet which includes the sackBlocks specified.
|
||||
func (c *Context) SendAckWithSACK(seq seqnum.Value, bytesReceived int, sackBlocks []header.SACKBlock) {
|
||||
options := make([]byte, 40)
|
||||
offset := 0
|
||||
if len(sackBlocks) > 0 {
|
||||
offset += header.EncodeNOP(options[offset:])
|
||||
offset += header.EncodeNOP(options[offset:])
|
||||
offset += header.EncodeSACKBlocks(sackBlocks, options[offset:])
|
||||
}
|
||||
|
||||
c.SendPacket(nil, &Headers{
|
||||
SrcPort: TestPort,
|
||||
DstPort: c.Port,
|
||||
Flags: header.TCPFlagAck,
|
||||
SeqNum: seqnum.Value(testInitialSequenceNumber).Add(1),
|
||||
SeqNum: seq,
|
||||
AckNum: c.IRS.Add(1 + seqnum.Size(bytesReceived)),
|
||||
RcvWnd: 30000,
|
||||
TCPOpts: options[:offset],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -369,9 +383,17 @@ func (c *Context) SendAck(seq seqnum.Value, bytesReceived int) {
|
||||
// verifies that the packet packet payload of packet matches the slice
|
||||
// of data indicated by offset & size.
|
||||
func (c *Context) ReceiveAndCheckPacket(data []byte, offset, size int) {
|
||||
c.ReceiveAndCheckPacketWithOptions(data, offset, size, 0)
|
||||
}
|
||||
|
||||
// ReceiveAndCheckPacketWithOptions reads a packet from the link layer endpoint
|
||||
// and verifies that the packet packet payload of packet matches the slice of
|
||||
// data indicated by offset & size and skips optlen bytes in addition to the IP
|
||||
// TCP headers when comparing the data.
|
||||
func (c *Context) ReceiveAndCheckPacketWithOptions(data []byte, offset, size, optlen int) {
|
||||
b := c.GetPacket()
|
||||
checker.IPv4(c.t, b,
|
||||
checker.PayloadLen(size+header.TCPMinimumSize),
|
||||
checker.PayloadLen(size+header.TCPMinimumSize+optlen),
|
||||
checker.TCP(
|
||||
checker.DstPort(TestPort),
|
||||
checker.SeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))),
|
||||
@@ -381,7 +403,7 @@ func (c *Context) ReceiveAndCheckPacket(data []byte, offset, size int) {
|
||||
)
|
||||
|
||||
pdata := data[offset:][:size]
|
||||
if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; bytes.Compare(pdata, p) != 0 {
|
||||
if p := b[header.IPv4MinimumSize+header.TCPMinimumSize+optlen:]; bytes.Compare(pdata, p) != 0 {
|
||||
c.t.Fatalf("Data is different: expected %v, got %v", pdata, p)
|
||||
}
|
||||
}
|
||||
@@ -683,12 +705,14 @@ func (c *Context) CreateConnectedWithOptions(wantOptions header.TCPSynOptions) *
|
||||
b := c.GetPacket()
|
||||
// Validate that the syn has the timestamp option and a valid
|
||||
// TS value.
|
||||
mss := uint16(c.linkEP.MTU() - header.IPv4MinimumSize - header.TCPMinimumSize)
|
||||
|
||||
checker.IPv4(c.t, b,
|
||||
checker.TCP(
|
||||
checker.DstPort(TestPort),
|
||||
checker.TCPFlags(header.TCPFlagSyn),
|
||||
checker.TCPSynOptions(header.TCPSynOptions{
|
||||
MSS: uint16(c.linkEP.MTU() - header.IPv4MinimumSize - header.TCPMinimumSize),
|
||||
MSS: mss,
|
||||
TS: true,
|
||||
WS: defaultWindowScale,
|
||||
SACKPermitted: c.SACKEnabled(),
|
||||
|
||||
Reference in New Issue
Block a user