Add tests for RACK

- Added packetimpact tests for RACK.

PiperOrigin-RevId: 353282342
This commit is contained in:
Nayana Bidari
2021-01-22 11:38:46 -08:00
committed by gVisor bot
parent 65594d30ad
commit 527ef5fc03
3 changed files with 239 additions and 0 deletions
+4
View File
@@ -277,6 +277,10 @@ ALL_TESTS = [
PacketimpactTestInfo(
name = "tcp_rcv_buf_space",
),
PacketimpactTestInfo(
name = "tcp_rack",
expect_netstack_failure = True,
),
]
def validate_all_tests():
+14
View File
@@ -376,6 +376,20 @@ packetimpact_testbench(
],
)
packetimpact_testbench(
name = "tcp_rack",
srcs = ["tcp_rack_test.go"],
deps = [
"//pkg/abi/linux",
"//pkg/binary",
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
"//pkg/usermem",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
],
)
validate_all_tests()
[packetimpact_go_test(
+221
View File
@@ -0,0 +1,221 @@
// 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_rack_test
import (
"flag"
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/binary"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
testbench.Initialize(flag.CommandLine)
}
const (
// payloadSize is the size used to send packets.
payloadSize = header.TCPDefaultMSS
// simulatedRTT is the time delay between packets sent and acked to
// increase the RTT.
simulatedRTT = 30 * time.Millisecond
// numPktsForRTT is the number of packets sent and acked to establish
// RTT.
numPktsForRTT = 10
)
func createSACKConnection(t *testing.T) (testbench.DUT, testbench.TCPIPv4, int32, int32) {
dut := testbench.NewDUT(t)
listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
// Enable SACK.
opts := make([]byte, 40)
optsOff := 0
optsOff += header.EncodeNOP(opts[optsOff:])
optsOff += header.EncodeNOP(opts[optsOff:])
optsOff += header.EncodeSACKPermittedOption(opts[optsOff:])
conn.ConnectWithOptions(t, opts[:optsOff])
acceptFd, _ := dut.Accept(t, listenFd)
return dut, conn, acceptFd, listenFd
}
func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, acceptFd, listenFd int32) {
dut.Close(t, acceptFd)
dut.Close(t, listenFd)
conn.Close(t)
}
func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) {
info := linux.TCPInfo{}
ret := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
binary.Unmarshal(ret, usermem.ByteOrder, &info)
return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
}
func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, numPkts int, acceptFd int32, sendACK bool) time.Time {
seqNum1 := *conn.RemoteSeqNum(t)
payload := make([]byte, payloadSize)
var lastSent time.Time
for i, sn := 0, seqNum1; i < numPkts; i++ {
lastSent = time.Now()
dut.Send(t, acceptFd, payload, 0)
gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
if err != nil {
t.Fatalf("Expect #%d: %s", i+1, err)
continue
}
if gotOne == nil {
t.Fatalf("#%d: expected a packet within a second but got none", i+1)
}
sn.UpdateForward(seqnum.Size(payloadSize))
if sendACK {
time.Sleep(simulatedRTT)
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))})
}
}
return lastSent
}
// TestRACKTLPAllPacketsLost tests TLP when an entire flight of data is lost.
func TestRACKTLPAllPacketsLost(t *testing.T) {
dut, conn, acceptFd, listenFd := createSACKConnection(t)
seqNum1 := *conn.RemoteSeqNum(t)
// Send ACK for data packets to establish RTT.
sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
// We are not sending ACK for these packets.
const numPkts = 5
lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
// Probe Timeout (PTO) should be two times RTT. Check that the last
// packet is retransmitted after probe timeout.
rtt, _ := getRTTAndRTO(t, dut, acceptFd)
pto := rtt * 2
// We expect the 5th packet (the last unacknowledged packet) to be
// retransmitted.
tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s %v %v", err, rtt, pto)
}
diff := time.Now().Sub(lastSent)
if diff < pto {
t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
}
closeSACKConnection(t, dut, conn, acceptFd, listenFd)
}
// TestRACKTLPLost tests TLP when there are tail losses.
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.4
func TestRACKTLPLost(t *testing.T) {
dut, conn, acceptFd, listenFd := createSACKConnection(t)
seqNum1 := *conn.RemoteSeqNum(t)
// Send ACK for data packets to establish RTT.
sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
// We are not sending ACK for these packets.
const numPkts = 10
lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
// Cumulative ACK for #[1-5] packets.
ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize))
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))})
// Probe Timeout (PTO) should be two times RTT. Check that the last
// packet is retransmitted after probe timeout.
rtt, _ := getRTTAndRTO(t, dut, acceptFd)
pto := rtt * 2
// We expect the 10th packet (the last unacknowledged packet) to be
// retransmitted.
tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s", err)
}
diff := time.Now().Sub(lastSent)
if diff < pto {
t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
}
closeSACKConnection(t, dut, conn, acceptFd, listenFd)
}
// TestRACKTLPWithSACK tests TLP by acknowledging out of order packets.
// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1
func TestRACKTLPWithSACK(t *testing.T) {
dut, conn, acceptFd, listenFd := createSACKConnection(t)
seqNum1 := *conn.RemoteSeqNum(t)
// Send ACK for data packets to establish RTT.
sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
// We are not sending ACK for these packets.
const numPkts = 3
lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
// SACK for #2 packet.
sackBlock := make([]byte, 40)
start := seqNum1.Add(seqnum.Size(payloadSize))
end := start.Add(seqnum.Size(payloadSize))
sbOff := 0
sbOff += header.EncodeNOP(sackBlock[sbOff:])
sbOff += header.EncodeNOP(sackBlock[sbOff:])
sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
start, end,
}}, sackBlock[sbOff:])
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
// RACK marks #1 packet as lost and retransmits it.
if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s", err)
}
// ACK for #1 packet.
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))})
// Probe Timeout (PTO) should be two times RTT. TLP will trigger for #3
// packet. RACK adds an additional timeout of 200ms if the number of
// outstanding packets is equal to 1.
rtt, rto := getRTTAndRTO(t, dut, acceptFd)
pto := rtt*2 + (200 * time.Millisecond)
if rto < pto {
pto = rto
}
// We expect the 3rd packet (the last unacknowledged packet) to be
// retransmitted.
tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s", err)
}
diff := time.Now().Sub(lastSent)
if diff < pto {
t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
}
closeSACKConnection(t, dut, conn, acceptFd, listenFd)
}