summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--test/packetimpact/runner/defs.bzl4
-rw-r--r--test/packetimpact/tests/BUILD14
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go221
3 files changed, 239 insertions, 0 deletions
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index c6c95546a..5c3c569de 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -277,6 +277,10 @@ ALL_TESTS = [
PacketimpactTestInfo(
name = "tcp_rcv_buf_space",
),
+ PacketimpactTestInfo(
+ name = "tcp_rack",
+ expect_netstack_failure = True,
+ ),
]
def validate_all_tests():
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index b1b3c578b..6c6f2bdf7 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -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(
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
new file mode 100644
index 000000000..0a2381c97
--- /dev/null
+++ b/test/packetimpact/tests/tcp_rack_test.go
@@ -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)
+}