diff options
Diffstat (limited to 'test/packetimpact')
-rw-r--r-- | test/packetimpact/dut/posix_server.cc | 35 | ||||
-rw-r--r-- | test/packetimpact/proto/posix_server.proto | 29 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 15 | ||||
-rw-r--r-- | test/packetimpact/testbench/dut.go | 106 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 14 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go | 130 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go | 131 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go | 287 |
8 files changed, 379 insertions, 368 deletions
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc index eba21df12..0d93b806e 100644 --- a/test/packetimpact/dut/posix_server.cc +++ b/test/packetimpact/dut/posix_server.cc @@ -180,16 +180,6 @@ class PosixImpl final : public posix_server::Posix::Service { return ::grpc::Status::OK; } - ::grpc::Status Fcntl(grpc::ServerContext *context, - const ::posix_server::FcntlRequest *request, - ::posix_server::FcntlResponse *response) override { - response->set_ret(::fcntl(request->fd(), request->cmd(), request->arg())); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - ::grpc::Status GetSockName( grpc::ServerContext *context, const ::posix_server::GetSockNameRequest *request, @@ -330,6 +320,31 @@ class PosixImpl final : public posix_server::Posix::Service { return ::grpc::Status::OK; } + ::grpc::Status SetNonblocking( + grpc::ServerContext *context, + const ::posix_server::SetNonblockingRequest *request, + ::posix_server::SetNonblockingResponse *response) override { + int flags = fcntl(request->fd(), F_GETFL); + if (flags == -1) { + response->set_ret(-1); + response->set_errno_(errno); + response->set_cmd("F_GETFL"); + return ::grpc::Status::OK; + } + if (request->nonblocking()) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + int ret = fcntl(request->fd(), F_SETFL, flags); + response->set_ret(ret); + if (ret == -1) { + response->set_errno_(errno); + response->set_cmd("F_SETFL"); + } + return ::grpc::Status::OK; + } + ::grpc::Status SetSockOpt( grpc::ServerContext *context, const ::posix_server::SetSockOptRequest *request, diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto index b4c68764a..521f03465 100644 --- a/test/packetimpact/proto/posix_server.proto +++ b/test/packetimpact/proto/posix_server.proto @@ -91,17 +91,6 @@ message ConnectResponse { int32 errno_ = 2; // "errno" may fail to compile in c++. } -message FcntlRequest { - int32 fd = 1; - int32 cmd = 2; - int32 arg = 3; -} - -message FcntlResponse { - int32 ret = 1; - int32 errno_ = 2; -} - message GetSockNameRequest { int32 sockfd = 1; } @@ -184,6 +173,18 @@ message SendToResponse { int32 errno_ = 2; // "errno" may fail to compile in c++. } +message SetNonblockingRequest { + int32 fd = 1; + bool nonblocking = 2; +} + +message SetNonblockingResponse { + int32 ret = 1; + int32 errno_ = 2; // "errno" may fail to compile in c++. + // The failed fcntl cmd. + string cmd = 3; +} + message SetSockOptRequest { int32 sockfd = 1; int32 level = 2; @@ -237,8 +238,6 @@ service Posix { rpc Close(CloseRequest) returns (CloseResponse); // Call connect() on the DUT. rpc Connect(ConnectRequest) returns (ConnectResponse); - // Call fcntl() on the DUT. - rpc Fcntl(FcntlRequest) returns (FcntlResponse); // Call getsockname() on the DUT. rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse); // Call getsockopt() on the DUT. @@ -253,6 +252,10 @@ service Posix { rpc Send(SendRequest) returns (SendResponse); // Call sendto() on the DUT. rpc SendTo(SendToRequest) returns (SendToResponse); + // Set/Clear O_NONBLOCK flag on the requested fd. This is needed because the + // operating system on DUT may have a different definition for O_NONBLOCK, it + // is not sound to assemble flags on testbench. + rpc SetNonblocking(SetNonblockingRequest) returns (SetNonblockingResponse); // Call setsockopt() on the DUT. rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse); // Call socket() on the DUT. diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 52ed5ae39..8ce5edf2b 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -128,14 +128,14 @@ def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_fa packetimpact_native_test( name = name, expect_failure = expect_native_failure, - testbench_binary = testbench_binary, num_duts = num_duts, + testbench_binary = testbench_binary, ) packetimpact_netstack_test( name = name, expect_failure = expect_netstack_failure, - testbench_binary = testbench_binary, num_duts = num_duts, + testbench_binary = testbench_binary, ) def packetimpact_testbench(name, size = "small", pure = True, **kwargs): @@ -161,7 +161,11 @@ def packetimpact_testbench(name, size = "small", pure = True, **kwargs): PacketimpactTestInfo = provider( doc = "Provide information for packetimpact tests", - fields = ["name", "expect_netstack_failure", "num_duts"], + fields = [ + "name", + "expect_netstack_failure", + "num_duts", + ], ) ALL_TESTS = [ @@ -219,7 +223,7 @@ ALL_TESTS = [ name = "tcp_zero_receive_window", ), PacketimpactTestInfo( - name = "tcp_queue_receive_in_syn_sent", + name = "tcp_queue_send_recv_in_syn_sent", ), PacketimpactTestInfo( name = "tcp_synsent_reset", @@ -242,9 +246,6 @@ ALL_TESTS = [ expect_netstack_failure = True, ), PacketimpactTestInfo( - name = "tcp_queue_send_in_syn_sent", - ), - PacketimpactTestInfo( name = "icmpv6_param_problem", ), PacketimpactTestInfo( diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 81634b5f4..be5121d98 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -189,10 +189,10 @@ func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) { func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { t.Helper() - req := pb.AcceptRequest{ + req := &pb.AcceptRequest{ Sockfd: sockfd, } - resp, err := dut.posixServer.Accept(ctx, &req) + resp, err := dut.posixServer.Accept(ctx, req) if err != nil { t.Fatalf("failed to call Accept: %s", err) } @@ -217,11 +217,11 @@ func (dut *DUT) Bind(t *testing.T, fd int32, sa unix.Sockaddr) { func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { t.Helper() - req := pb.BindRequest{ + req := &pb.BindRequest{ Sockfd: fd, Addr: dut.sockaddrToProto(t, sa), } - resp, err := dut.posixServer.Bind(ctx, &req) + resp, err := dut.posixServer.Bind(ctx, req) if err != nil { t.Fatalf("failed to call Bind: %s", err) } @@ -246,10 +246,10 @@ func (dut *DUT) Close(t *testing.T, fd int32) { func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int32, error) { t.Helper() - req := pb.CloseRequest{ + req := &pb.CloseRequest{ Fd: fd, } - resp, err := dut.posixServer.Close(ctx, &req) + resp, err := dut.posixServer.Close(ctx, req) if err != nil { t.Fatalf("failed to call Close: %s", err) } @@ -276,48 +276,17 @@ func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) { func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { t.Helper() - req := pb.ConnectRequest{ + req := &pb.ConnectRequest{ Sockfd: fd, Addr: dut.sockaddrToProto(t, sa), } - resp, err := dut.posixServer.Connect(ctx, &req) + resp, err := dut.posixServer.Connect(ctx, req) if err != nil { t.Fatalf("failed to call Connect: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } -// Fcntl calls fcntl on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use FcntlWithErrno. -func (dut *DUT) Fcntl(t *testing.T, fd, cmd, arg int32) int32 { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.FcntlWithErrno(ctx, t, fd, cmd, arg) - if ret == -1 { - t.Fatalf("failed to Fcntl: ret=%d, errno=%s", ret, err) - } - return ret -} - -// FcntlWithErrno calls fcntl on the DUT. -func (dut *DUT) FcntlWithErrno(ctx context.Context, t *testing.T, fd, cmd, arg int32) (int32, error) { - t.Helper() - - req := pb.FcntlRequest{ - Fd: fd, - Cmd: cmd, - Arg: arg, - } - resp, err := dut.posixServer.Fcntl(ctx, &req) - if err != nil { - t.Fatalf("failed to call Fcntl: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - // GetSockName calls getsockname on the DUT and causes a fatal test failure if // it doesn't succeed. If more control over the timeout or error handling is // needed, use GetSockNameWithErrno. @@ -337,10 +306,10 @@ func (dut *DUT) GetSockName(t *testing.T, sockfd int32) unix.Sockaddr { func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { t.Helper() - req := pb.GetSockNameRequest{ + req := &pb.GetSockNameRequest{ Sockfd: sockfd, } - resp, err := dut.posixServer.GetSockName(ctx, &req) + resp, err := dut.posixServer.GetSockName(ctx, req) if err != nil { t.Fatalf("failed to call Bind: %s", err) } @@ -350,14 +319,14 @@ func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd i func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) { t.Helper() - req := pb.GetSockOptRequest{ + req := &pb.GetSockOptRequest{ Sockfd: sockfd, Level: level, Optname: optname, Optlen: optlen, Type: typ, } - resp, err := dut.posixServer.GetSockOpt(ctx, &req) + resp, err := dut.posixServer.GetSockOpt(ctx, req) if err != nil { t.Fatalf("failed to call GetSockOpt: %s", err) } @@ -475,11 +444,11 @@ func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) { func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backlog int32) (int32, error) { t.Helper() - req := pb.ListenRequest{ + req := &pb.ListenRequest{ Sockfd: sockfd, Backlog: backlog, } - resp, err := dut.posixServer.Listen(ctx, &req) + resp, err := dut.posixServer.Listen(ctx, req) if err != nil { t.Fatalf("failed to call Listen: %s", err) } @@ -527,7 +496,7 @@ func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) [] func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) { t.Helper() - req := pb.PollRequest{ + req := &pb.PollRequest{ TimeoutMillis: int32(timeout.Milliseconds()), } for _, pfd := range pfds { @@ -536,7 +505,7 @@ func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.Pol Events: uint32(pfd.Events), }) } - resp, err := dut.posixServer.Poll(ctx, &req) + resp, err := dut.posixServer.Poll(ctx, req) if err != nil { t.Fatalf("failed to call Poll: %s", err) } @@ -572,12 +541,12 @@ func (dut *DUT) Send(t *testing.T, sockfd int32, buf []byte, flags int32) int32 func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32) (int32, error) { t.Helper() - req := pb.SendRequest{ + req := &pb.SendRequest{ Sockfd: sockfd, Buf: buf, Flags: flags, } - resp, err := dut.posixServer.Send(ctx, &req) + resp, err := dut.posixServer.Send(ctx, req) if err != nil { t.Fatalf("failed to call Send: %s", err) } @@ -603,13 +572,13 @@ func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, dest func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) { t.Helper() - req := pb.SendToRequest{ + req := &pb.SendToRequest{ Sockfd: sockfd, Buf: buf, Flags: flags, DestAddr: dut.sockaddrToProto(t, destAddr), } - resp, err := dut.posixServer.SendTo(ctx, &req) + resp, err := dut.posixServer.SendTo(ctx, req) if err != nil { t.Fatalf("failed to call SendTo: %s", err) } @@ -621,25 +590,32 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) { t.Helper() - flags := dut.Fcntl(t, fd, unix.F_GETFL, 0) - if nonblocking { - flags |= unix.O_NONBLOCK - } else { - flags &= ^unix.O_NONBLOCK + req := &pb.SetNonblockingRequest{ + Fd: fd, + Nonblocking: nonblocking, + } + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + + resp, err := dut.posixServer.SetNonblocking(ctx, req) + if err != nil { + t.Fatalf("failed to call SetNonblocking: %s", err) + } + if resp.GetRet() == -1 { + t.Fatalf("fcntl(%d, %s) failed: %s", fd, resp.GetCmd(), syscall.Errno(resp.GetErrno_())) } - dut.Fcntl(t, fd, unix.F_SETFL, flags) } func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) { t.Helper() - req := pb.SetSockOptRequest{ + req := &pb.SetSockOptRequest{ Sockfd: sockfd, Level: level, Optname: optname, Optval: optval, } - resp, err := dut.posixServer.SetSockOpt(ctx, &req) + resp, err := dut.posixServer.SetSockOpt(ctx, req) if err != nil { t.Fatalf("failed to call SetSockOpt: %s", err) } @@ -734,13 +710,13 @@ func (dut *DUT) Socket(t *testing.T, domain, typ, proto int32) int32 { func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, error) { t.Helper() - req := pb.SocketRequest{ + req := &pb.SocketRequest{ Domain: domain, Type: typ, Protocol: proto, } ctx := context.Background() - resp, err := dut.posixServer.Socket(ctx, &req) + resp, err := dut.posixServer.Socket(ctx, req) if err != nil { t.Fatalf("failed to call Socket: %s", err) } @@ -766,12 +742,12 @@ func (dut *DUT) Recv(t *testing.T, sockfd, len, flags int32) []byte { func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, flags int32) (int32, []byte, error) { t.Helper() - req := pb.RecvRequest{ + req := &pb.RecvRequest{ Sockfd: sockfd, Len: len, Flags: flags, } - resp, err := dut.posixServer.Recv(ctx, &req) + resp, err := dut.posixServer.Recv(ctx, req) if err != nil { t.Fatalf("failed to call Recv: %s", err) } @@ -807,11 +783,11 @@ func (dut *DUT) Shutdown(t *testing.T, fd, how int32) error { func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) error { t.Helper() - req := pb.ShutdownRequest{ + req := &pb.ShutdownRequest{ Fd: fd, How: how, } - resp, err := dut.posixServer.Shutdown(ctx, &req) + resp, err := dut.posixServer.Shutdown(ctx, req) if err != nil { t.Fatalf("failed to call Shutdown: %s", err) } diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 3e27f50dc..301cf4980 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -176,8 +176,8 @@ packetimpact_testbench( ) packetimpact_testbench( - name = "tcp_queue_receive_in_syn_sent", - srcs = ["tcp_queue_receive_in_syn_sent_test.go"], + name = "tcp_queue_send_recv_in_syn_sent", + srcs = ["tcp_queue_send_recv_in_syn_sent_test.go"], deps = [ "//pkg/tcpip/header", "//test/packetimpact/testbench", @@ -246,16 +246,6 @@ packetimpact_testbench( ) packetimpact_testbench( - name = "tcp_queue_send_in_syn_sent", - srcs = ["tcp_queue_send_in_syn_sent_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( name = "icmpv6_param_problem", srcs = ["icmpv6_param_problem_test.go"], deps = [ diff --git a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go deleted file mode 100644 index 646c93216..000000000 --- a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// 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_queue_receive_in_syn_sent_test - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "flag" - "sync" - "syscall" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestQueueReceiveInSynSent tests receive behavior when the TCP state -// is SYN-SENT. -// It tests for 2 variants where the receive is blocked and: -// (1) we complete handshake and send sample data. -// (2) we send a TCP RST. -func TestQueueReceiveInSynSent(t *testing.T) { - for _, tt := range []struct { - description string - reset bool - }{ - {description: "Send DATA", reset: false}, - {description: "Send RST", reset: true}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) { - t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) - } - - // Test blocking read. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue RECEIVE call in SYN-SENT, this should be queued for - // process until the connection is established. - n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) - if tt.reset { - if err != syscall.Errno(unix.ECONNREFUSED) { - t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - return - } - if n == -1 { - t.Errorf("failed to recv on DUT: %s", err) - } - if got := buff[:n]; !bytes.Equal(got, sampleData) { - t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData)) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint receive. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked on Recv. - time.Sleep(100 * time.Millisecond) - - if tt.reset { - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) - return - } - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - - // Send sample payload and expect an ACK. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go deleted file mode 100644 index 29e51cae3..000000000 --- a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// 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_queue_send_in_syn_sent_test - -import ( - "context" - "errors" - "flag" - "sync" - "syscall" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestQueueSendInSynSent tests send behavior when the TCP state -// is SYN-SENT. -// It tests for 2 variants when in SYN_SENT state and: -// (1) DUT blocks on send and complete handshake -// (2) DUT blocks on send and receive a TCP RST. -func TestQueueSendInSynSent(t *testing.T) { - for _, tt := range []struct { - description string - reset bool - }{ - {description: "Complete handshake", reset: false}, - {description: "Send RST", reset: true}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - if _, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != syscall.Errno(unix.EWOULDBLOCK) { - t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) - } - - // Test blocking write. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue SEND call in SYN-SENT, this should be queued for - // process until the connection is established. - n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) - if tt.reset { - if err != syscall.Errno(unix.ECONNREFUSED) { - t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - return - } - if n != int32(len(sampleData)) { - t.Errorf("failed to send on DUT: %s", err) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked on send. - time.Sleep(100 * time.Millisecond) - - if tt.reset { - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) - return - } - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) - - // Expect the data from the DUT's enqueued send request. - // - // On Linux, this can be piggybacked with the ACK completing the - // handshake. On gVisor, getting such a piggyback is a bit more - // complicated because the actual data enqueuing occurs in the - // callers of endpoint Write. - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Send sample payload and expect an ACK to ensure connection is still ESTABLISHED. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go new file mode 100644 index 000000000..7dd1c326a --- /dev/null +++ b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go @@ -0,0 +1,287 @@ +// 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_queue_send_recv_in_syn_sent_test + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "flag" + "sync" + "syscall" + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.Initialize(flag.CommandLine) +} + +// TestQueueSendInSynSentHandshake tests send behavior when the TCP state +// is SYN-SENT and the connections is finally established. +func TestQueueSendInSynSentHandshake(t *testing.T) { + dut := testbench.NewDUT(t) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + sampleData := []byte("Sample Data") + + dut.SetNonBlocking(t, socket, true) + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) + } + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + t.Fatalf("expected a SYN from DUT, but got none: %s", err) + } + + // Test blocking send. + dut.SetNonBlocking(t, socket, false) + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + var block sync.WaitGroup + block.Add(1) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + block.Done() + // Issue SEND call in SYN-SENT, this should be queued for + // process until the connection is established. + n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) + if n == -1 { + t.Errorf("failed to send on DUT: %s", err) + return + } + }() + + // Wait for the goroutine to be scheduled and before it + // blocks on endpoint send/receive. + block.Wait() + // The following sleep is used to prevent the connection + // from being established before we are blocked: there is + // still a small time window between we sending the RPC + // request and the system actually being blocked. + time.Sleep(100 * time.Millisecond) + + // Bring the connection to Established. + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) + // Expect the data from the DUT's enqueued send request. + // + // On Linux, this can be piggybacked with the ACK completing the + // handshake. On gVisor, getting such a piggyback is a bit more + // complicated because the actual data enqueuing occurs in the + // callers of endpoint Write. + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { + t.Fatalf("expected payload was not received: %s", err) + } + + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected an ACK from DUT, but got none: %s", err) + } +} + +// TestQueueRecvInSynSentHandshake tests recv behavior when the TCP state +// is SYN-SENT and the connections is finally established. +func TestQueueRecvInSynSentHandshake(t *testing.T) { + dut := testbench.NewDUT(t) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + sampleData := []byte("Sample Data") + + dut.SetNonBlocking(t, socket, true) + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) + } + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + t.Fatalf("expected a SYN from DUT, but got none: %s", err) + } + + if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) { + t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) + } + + // Test blocking read. + dut.SetNonBlocking(t, socket, false) + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + var block sync.WaitGroup + block.Add(1) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + block.Done() + // Issue RECEIVE call in SYN-SENT, this should be queued for + // process until the connection is established. + n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) + if n == -1 { + t.Errorf("failed to recv on DUT: %s", err) + return + } + if got := buff[:n]; !bytes.Equal(got, sampleData) { + t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData)) + } + }() + + // Wait for the goroutine to be scheduled and before it + // blocks on endpoint send/receive. + block.Wait() + // The following sleep is used to prevent the connection + // from being established before we are blocked: there is + // still a small time window between we sending the RPC + // request and the system actually being blocked. + time.Sleep(100 * time.Millisecond) + + // Bring the connection to Established. + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected an ACK from DUT, but got none: %s", err) + } + + // Send sample payload so that DUT can recv. + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected an ACK from DUT, but got none: %s", err) + } +} + +// TestQueueSendInSynSentRST tests send behavior when the TCP state +// is SYN-SENT and an RST is sent. +func TestQueueSendInSynSentRST(t *testing.T) { + dut := testbench.NewDUT(t) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + sampleData := []byte("Sample Data") + + dut.SetNonBlocking(t, socket, true) + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) + } + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + t.Fatalf("expected a SYN from DUT, but got none: %s", err) + } + + // Test blocking send. + dut.SetNonBlocking(t, socket, false) + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + var block sync.WaitGroup + block.Add(1) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + block.Done() + // Issue SEND call in SYN-SENT, this should be queued for + // process until the connection is established. + n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) + if err != syscall.Errno(unix.ECONNREFUSED) { + t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) + } + if n != -1 { + t.Errorf("expected return value %d, got %d", -1, n) + } + }() + + // Wait for the goroutine to be scheduled and before it + // blocks on endpoint send/receive. + block.Wait() + // The following sleep is used to prevent the connection + // from being established before we are blocked: there is + // still a small time window between we sending the RPC + // request and the system actually being blocked. + time.Sleep(100 * time.Millisecond) + + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) +} + +// TestQueueRecvInSynSentRST tests recv behavior when the TCP state +// is SYN-SENT and an RST is sent. +func TestQueueRecvInSynSentRST(t *testing.T) { + dut := testbench.NewDUT(t) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + sampleData := []byte("Sample Data") + + dut.SetNonBlocking(t, socket, true) + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) + } + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + t.Fatalf("expected a SYN from DUT, but got none: %s", err) + } + + if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) { + t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) + } + + // Test blocking read. + dut.SetNonBlocking(t, socket, false) + + var wg sync.WaitGroup + defer wg.Wait() + wg.Add(1) + var block sync.WaitGroup + block.Add(1) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + block.Done() + // Issue RECEIVE call in SYN-SENT, this should be queued for + // process until the connection is established. + n, _, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) + if err != syscall.Errno(unix.ECONNREFUSED) { + t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) + } + if n != -1 { + t.Errorf("expected return value %d, got %d", -1, n) + } + }() + + // Wait for the goroutine to be scheduled and before it + // blocks on endpoint send/receive. + block.Wait() + // The following sleep is used to prevent the connection + // from being established before we are blocked: there is + // still a small time window between we sending the RPC + // request and the system actually being blocked. + time.Sleep(100 * time.Millisecond) + + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) +} |