summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact/tests/tcp_rack_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact/tests/tcp_rack_test.go')
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go204
1 files changed, 202 insertions, 2 deletions
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
index 0a2381c97..fb2a4cc90 100644
--- a/test/packetimpact/tests/tcp_rack_test.go
+++ b/test/packetimpact/tests/tcp_rack_test.go
@@ -70,8 +70,11 @@ func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4
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)
+ infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
}
@@ -219,3 +222,200 @@ func TestRACKTLPWithSACK(t *testing.T) {
}
closeSACKConnection(t, dut, conn, acceptFd, listenFd)
}
+
+// TestRACKWithoutReorder tests that without reordering RACK will retransmit the
+// lost packets after reorder timer expires.
+func TestRACKWithoutReorder(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 = 4
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // SACK for [3,4] packets.
+ sackBlock := make([]byte, 40)
+ start := seqNum1.Add(seqnum.Size(2 * payloadSize))
+ end := start.Add(seqnum.Size(2 * payloadSize))
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock[sbOff:])
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
+
+ // RACK marks #1 and #2 packets as lost and retransmits both after
+ // RTT + reorderWindow. The reorderWindow initially will be a small
+ // fraction of RTT.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ timeout := 2 * rtt
+ for i, sn := 0, seqNum1; i < 2; i++ {
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ sn.UpdateForward(seqnum.Size(payloadSize))
+ }
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKWithReorder tests that RACK will retransmit segments when there is
+// reordering in the connection and reorder timer expires.
+func TestRACKWithReorder(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 = 4
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ time.Sleep(simulatedRTT)
+ // SACK in reverse order for the connection to detect reorder.
+ var start seqnum.Value
+ var end seqnum.Value
+ for i := 0; i < numPkts-1; i++ {
+ sackBlock := make([]byte, 40)
+ sbOff := 0
+ start = seqNum1.Add(seqnum.Size((numPkts - i - 1) * payloadSize))
+ end = start.Add(seqnum.Size((i + 1) * payloadSize))
+ sackBlock = make([]byte, 40)
+ 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]})
+ }
+
+ // Send a DSACK block indicating both original and retransmitted
+ // packets are received, RACK will increase the reordering window on
+ // every DSACK.
+ dsackBlock := make([]byte, 40)
+ dbOff := 0
+ start = seqNum1
+ end = start.Add(seqnum.Size(2 * payloadSize))
+ dbOff += header.EncodeNOP(dsackBlock[dbOff:])
+ dbOff += header.EncodeNOP(dsackBlock[dbOff:])
+ dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, dsackBlock[dbOff:])
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]})
+
+ seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize))
+ sendTime := time.Now()
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ time.Sleep(simulatedRTT)
+ // Send SACK for [2-5] packets.
+ sackBlock := make([]byte, 40)
+ sbOff := 0
+ start = seqNum1.Add(seqnum.Size(payloadSize))
+ end = start.Add(seqnum.Size(3 * payloadSize))
+ 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]})
+
+ // Expect the retransmission of #1 packet after RTT+ReorderWindow.
+ 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)
+ }
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ diff := time.Now().Sub(sendTime)
+ if diff < rtt {
+ t.Fatalf("expected payload was received too sonn, within RTT")
+ }
+
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKWithLostRetransmission tests that RACK will not enter RTO when a
+// retransmitted segment is lost and enters fast recovery.
+func TestRACKWithLostRetransmission(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
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // SACK for [2-5] packets.
+ sackBlock := make([]byte, 40)
+ start := seqNum1.Add(seqnum.Size(payloadSize))
+ end := start.Add(seqnum.Size(4 * payloadSize))
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock[sbOff:])
+ time.Sleep(simulatedRTT)
+ 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 after
+ // RTT + reorderWindow. The reorderWindow is bounded between a small
+ // fraction of RTT and 1 RTT.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ timeout := 2 * rtt
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Send #6 packet.
+ payload := make([]byte, payloadSize)
+ dut.Send(t, acceptFd, payload, 0)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1 + 5*payloadSize))}, time.Second)
+ if err != nil {
+ t.Fatalf("Expect #6: %s", err)
+ }
+ if gotOne == nil {
+ t.Fatalf("#6: expected a packet within a second but got none")
+ }
+
+ // SACK for [2-6] packets.
+ sackBlock1 := make([]byte, 40)
+ start = seqNum1.Add(seqnum.Size(payloadSize))
+ end = start.Add(seqnum.Size(5 * payloadSize))
+ sbOff1 := 0
+ sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
+ sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
+ sbOff1 += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock1[sbOff1:])
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]})
+
+ // Expect re-retransmission of #1 packet without entering an RTO.
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Check the congestion control state.
+ info := linux.TCPInfo{}
+ infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
+ if info.CaState != linux.TCP_CA_Recovery {
+ t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState)
+ }
+
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}