Implement support for SACK based recovery(RFC 6675).

PiperOrigin-RevId: 246536003
Change-Id: I118b745f45040be9c70cb6a1028acdb06c78d8c9
This commit is contained in:
Bhasker Hariharan
2019-05-03 10:51:18 -07:00
committed by Shentubot
parent 95614bbefa
commit 458fe955a7
10 changed files with 966 additions and 229 deletions
+13 -1
View File
@@ -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.
+1 -1
View File
@@ -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
}
+6 -4
View File
@@ -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,
+65 -8
View File
@@ -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)
}
}
}
+5
View File
@@ -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())}
}
File diff suppressed because it is too large Load Diff
+215 -1
View File
@@ -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++
}
}
+6 -5
View File
@@ -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(),