diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/e2e/integration_test.go | 74 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_outside_the_window_test.go | 18 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_rack_test.go | 204 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ip_tcp_generic.cc | 1 | ||||
-rw-r--r-- | test/syscalls/linux/tcp_socket.cc | 36 |
5 files changed, 275 insertions, 58 deletions
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index d07ed6ba5..aaffabfd0 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -434,18 +434,7 @@ func TestTmpMount(t *testing.T) { // runsc to hide the incoherence of FDs opened before and after overlayfs // copy-up on the host. func TestHostOverlayfsCopyUp(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/hostoverlaytest", - WorkDir: "/root", - }, "./test_copy_up"); err != nil { - t.Fatalf("docker run failed: %v", err) - } else if got != "" { - t.Errorf("test failed:\n%s", got) - } + runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o test_copy_up test_copy_up.c && ./test_copy_up") } // TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory @@ -460,36 +449,14 @@ func TestHostOverlayfsCopyUp(t *testing.T) { // automated tests yield newly-added files from readdir() even if the fsgofer // does not explicitly rewinddir(), but overlayfs does not. func TestHostOverlayfsRewindDir(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/hostoverlaytest", - WorkDir: "/root", - }, "./test_rewinddir"); err != nil { - t.Fatalf("docker run failed: %v", err) - } else if got != "" { - t.Errorf("test failed:\n%s", got) - } + runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o test_rewinddir test_rewinddir.c && ./test_rewinddir") } // Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it // cannot use tricks like userns as root. For this reason, run a basic link test // to ensure some coverage. func TestLink(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/linktest", - WorkDir: "/root", - }, "./link_test"); err != nil { - t.Fatalf("docker run failed: %v", err) - } else if got != "" { - t.Errorf("test failed:\n%s", got) - } + runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o link_test link_test.c && ./link_test") } // This test ensures we can run ping without errors. @@ -500,17 +467,7 @@ func TestPing4Loopback(t *testing.T) { t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.") } - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/ping4test", - }, "/root/ping4.sh"); err != nil { - t.Fatalf("docker run failed: %s", err) - } else if got != "" { - t.Errorf("test failed:\n%s", got) - } + runIntegrationTest(t, nil, "./ping4.sh") } // This test ensures we can enable ipv6 on loopback and run ping6 without @@ -522,20 +479,25 @@ func TestPing6Loopback(t *testing.T) { t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.") } + // The CAP_NET_ADMIN capability is required to use the `ip` utility, which + // we use to enable ipv6 on loopback. + // + // By default, ipv6 loopback is not enabled by runsc, because docker does + // not assign an ipv6 address to the test container. + runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh") +} + +func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { ctx := context.Background() d := dockerutil.MakeContainer(ctx, t) defer d.CleanUp(ctx) if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/ping6test", - // The CAP_NET_ADMIN capability is required to use the `ip` utility, which - // we use to enable ipv6 on loopback. - // - // By default, ipv6 loopback is not enabled by runsc, because docker does - // not assign an ipv6 address to the test container. - CapAdd: []string{"NET_ADMIN"}, - }, "/root/ping6.sh"); err != nil { - t.Fatalf("docker run failed: %s", err) + Image: "basic/integrationtest", + WorkDir: "/root", + CapAdd: capAdd, + }, args...); err != nil { + t.Fatalf("docker run failed: %v", err) } else if got != "" { t.Errorf("test failed:\n%s", got) } diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go index 1b041932a..8909a348e 100644 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ b/test/packetimpact/tests/tcp_outside_the_window_test.go @@ -84,6 +84,24 @@ func TestTCPOutsideTheWindow(t *testing.T) { if tt.expectACK && err != nil { t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err) } + // Data packets w/o SYN bits are always acked by Linux. Netstack ACK's data packets + // always right now. So only send a second segment and test for no ACK for packets + // with no data. + if tt.expectACK && tt.payload == nil { + // Sending another out-of-window segment immediately should not trigger + // an ACK if less than 500ms(default rate limit for out-of-window ACKs) + // has passed since the last ACK was sent. + t.Logf("sending another segment") + conn.Send(t, testbench.TCP{ + Flags: testbench.Uint8(tt.tcpFlags), + SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), + }, tt.payload...) + timeout := 3 * time.Second + gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout) + if err == nil { + t.Fatalf("expected no ACK packet but got one: %s", gotACK) + } + } if !tt.expectACK && gotACK != nil { t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK) } 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) +} diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc index 2c8e5f6f3..579e824cd 100644 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ b/test/syscalls/linux/socket_ip_tcp_generic.cc @@ -88,6 +88,7 @@ TEST_P(TCPSocketPairTest, CheckTcpInfoFields) { socklen_t optLen = sizeof(opt); ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), SyscallSucceeds()); + ASSERT_EQ(optLen, sizeof(opt)); // Validates the received tcp_info fields. EXPECT_EQ(opt.tcpi_ca_state, TCP_CA_OPEN); diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 9028ab024..f56c50e61 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1168,6 +1168,42 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { EXPECT_EQ(read_bytes, kBufSz); } +TEST_P(SimpleTcpSocketTest, SelfConnectSend_NoRandomSave) { + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + constexpr int max_seg = 256; + ASSERT_THAT( + setsockopt(s.get(), SOL_TCP, TCP_MAXSEG, &max_seg, sizeof(max_seg)), + SyscallSucceeds()); + + ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + // Get the bound port. + ASSERT_THAT( + getsockname(s.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + std::vector<char> writebuf(512 << 10); // 512 KiB. + + // Try to send the whole thing. + int n; + ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), writebuf.size(), 0), + SyscallSucceeds()); + + // We should have written the whole thing. + EXPECT_EQ(n, writebuf.size()); + EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); +} + TEST_P(SimpleTcpSocketTest, NonBlockingConnect) { const FileDescriptor listener = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); |