diff options
Diffstat (limited to 'test/packetimpact/tests')
-rw-r--r-- | test/packetimpact/tests/BUILD | 14 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_rack_test.go | 221 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go | 28 |
3 files changed, 252 insertions, 11 deletions
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) +} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go index 1ab9ee1b2..b15b8fc25 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go @@ -66,33 +66,39 @@ func TestZeroWindowProbeRetransmit(t *testing.T) { probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - startProbeDuration := time.Second - current := startProbeDuration - first := time.Now() // Ask the dut to send out data. dut.Send(t, acceptFd, sampleData, 0) + + var prev time.Duration // Expect the dut to keep the connection alive as long as the remote is // acknowledging the zero-window probes. - for i := 0; i < 5; i++ { + for i := 1; i <= 5; i++ { start := time.Now() // Expect zero-window probe with a timeout which is a function of the typical // first retransmission time. The retransmission times is supposed to // exponentially increase. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Duration(i)*time.Second); err != nil { t.Fatalf("expected a probe with sequence number %d: loop %d", probeSeq, i) } - if i == 0 { - startProbeDuration = time.Now().Sub(first) - current = 2 * startProbeDuration + if i == 1 { + // Skip the first probe as computing transmit time for that is + // non-deterministic because of the arbitrary time taken for + // the dut to receive a send command and issue a send. continue } - // Check if the probes came at exponentially increasing intervals. - if got, want := time.Since(start), current-startProbeDuration; got < want { + + // Check if the time taken to receive the probe from the dut is + // increasing exponentially. To avoid flakes, use a correction + // factor for the expected duration which accounts for any + // scheduling non-determinism. + const timeCorrection = 200 * time.Millisecond + got := time.Since(start) + if want := (2 * prev) - timeCorrection; prev != 0 && got < want { t.Errorf("got zero probe %d after %s, want >= %s", i, got, want) } + prev = got // Acknowledge the zero-window probes from the dut. conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - current *= 2 } // Advertize non-zero window. conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)}) |