summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/e2e/integration_test.go74
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go18
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go204
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc1
-rw-r--r--test/syscalls/linux/tcp_socket.cc36
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));