diff options
Diffstat (limited to 'test/packetimpact')
-rw-r--r-- | test/packetimpact/dut/posix_server.cc | 119 | ||||
-rw-r--r-- | test/packetimpact/proto/posix_server.proto | 87 | ||||
-rw-r--r-- | test/packetimpact/testbench/dut.go | 118 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 32 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go | 99 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_zero_window_probe_test.go | 107 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go | 93 |
7 files changed, 462 insertions, 193 deletions
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc index 2ebd3b95b..dc3024f44 100644 --- a/test/packetimpact/dut/posix_server.cc +++ b/test/packetimpact/dut/posix_server.cc @@ -174,41 +174,41 @@ class PosixImpl final : public posix_server::Posix::Service { grpc_impl::ServerContext *context, const ::posix_server::GetSockOptRequest *request, ::posix_server::GetSockOptResponse *response) override { - socklen_t optlen = request->optlen(); - std::vector<char> buf(optlen); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), buf.data(), &optlen)); - response->set_errno_(errno); - if (optlen >= 0) { - response->set_optval(buf.data(), optlen); + switch (request->type()) { + case ::posix_server::GetSockOptRequest::BYTES: { + socklen_t optlen = request->optlen(); + std::vector<char> buf(optlen); + response->set_ret(::getsockopt(request->sockfd(), request->level(), + request->optname(), buf.data(), + &optlen)); + if (optlen >= 0) { + response->mutable_optval()->set_bytesval(buf.data(), optlen); + } + break; + } + case ::posix_server::GetSockOptRequest::INT: { + int intval = 0; + socklen_t optlen = sizeof(intval); + response->set_ret(::getsockopt(request->sockfd(), request->level(), + request->optname(), &intval, &optlen)); + response->mutable_optval()->set_intval(intval); + break; + } + case ::posix_server::GetSockOptRequest::TIME: { + timeval tv; + socklen_t optlen = sizeof(tv); + response->set_ret(::getsockopt(request->sockfd(), request->level(), + request->optname(), &tv, &optlen)); + response->mutable_optval()->mutable_timeval()->set_seconds(tv.tv_sec); + response->mutable_optval()->mutable_timeval()->set_microseconds( + tv.tv_usec); + break; + } + default: + return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Unknown SockOpt Type"); } - return ::grpc::Status::OK; - } - - ::grpc::Status GetSockOptInt( - ::grpc::ServerContext *context, - const ::posix_server::GetSockOptIntRequest *request, - ::posix_server::GetSockOptIntResponse *response) override { - int opt = 0; - socklen_t optlen = sizeof(opt); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), &opt, &optlen)); - response->set_errno_(errno); - response->set_intval(opt); - return ::grpc::Status::OK; - } - - ::grpc::Status GetSockOptTimeval( - ::grpc::ServerContext *context, - const ::posix_server::GetSockOptTimevalRequest *request, - ::posix_server::GetSockOptTimevalResponse *response) override { - timeval tv; - socklen_t optlen = sizeof(tv); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), &tv, &optlen)); response->set_errno_(errno); - response->mutable_timeval()->set_seconds(tv.tv_sec); - response->mutable_timeval()->set_microseconds(tv.tv_usec); return ::grpc::Status::OK; } @@ -253,33 +253,32 @@ class PosixImpl final : public posix_server::Posix::Service { grpc_impl::ServerContext *context, const ::posix_server::SetSockOptRequest *request, ::posix_server::SetSockOptResponse *response) override { - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), request->optval().c_str(), - request->optval().size())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOptInt( - ::grpc::ServerContext *context, - const ::posix_server::SetSockOptIntRequest *request, - ::posix_server::SetSockOptIntResponse *response) override { - int opt = request->intval(); - response->set_ret(::setsockopt(request->sockfd(), request->level(), - request->optname(), &opt, sizeof(opt))); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOptTimeval( - ::grpc::ServerContext *context, - const ::posix_server::SetSockOptTimevalRequest *request, - ::posix_server::SetSockOptTimevalResponse *response) override { - timeval tv = {.tv_sec = static_cast<__time_t>(request->timeval().seconds()), - .tv_usec = static_cast<__suseconds_t>( - request->timeval().microseconds())}; - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), &tv, sizeof(tv))); + switch (request->optval().val_case()) { + case ::posix_server::SockOptVal::kBytesval: + response->set_ret(setsockopt(request->sockfd(), request->level(), + request->optname(), + request->optval().bytesval().c_str(), + request->optval().bytesval().size())); + break; + case ::posix_server::SockOptVal::kIntval: { + int opt = request->optval().intval(); + response->set_ret(::setsockopt(request->sockfd(), request->level(), + request->optname(), &opt, sizeof(opt))); + break; + } + case ::posix_server::SockOptVal::kTimeval: { + timeval tv = {.tv_sec = static_cast<__time_t>( + request->optval().timeval().seconds()), + .tv_usec = static_cast<__suseconds_t>( + request->optval().timeval().microseconds())}; + response->set_ret(setsockopt(request->sockfd(), request->level(), + request->optname(), &tv, sizeof(tv))); + break; + } + default: + return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Unknown SockOpt Type"); + } response->set_errno_(errno); return ::grpc::Status::OK; } diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto index ab5ba1c85..9dca563f1 100644 --- a/test/packetimpact/proto/posix_server.proto +++ b/test/packetimpact/proto/posix_server.proto @@ -42,6 +42,14 @@ message Timeval { int64 microseconds = 2; } +message SockOptVal { + oneof val { + bytes bytesval = 1; + int32 intval = 2; + Timeval timeval = 3; + } +} + // Request and Response pairs for each Posix service RPC call, sorted. message AcceptRequest { @@ -98,36 +106,19 @@ message GetSockOptRequest { int32 level = 2; int32 optname = 3; int32 optlen = 4; + enum SockOptType { + UNSPECIFIED = 0; + BYTES = 1; + INT = 2; + TIME = 3; + } + SockOptType type = 5; } message GetSockOptResponse { int32 ret = 1; int32 errno_ = 2; // "errno" may fail to compile in c++. - bytes optval = 3; -} - -message GetSockOptIntRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; -} - -message GetSockOptIntResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - int32 intval = 3; -} - -message GetSockOptTimevalRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; -} - -message GetSockOptTimevalResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - Timeval timeval = 3; + SockOptVal optval = 3; } message ListenRequest { @@ -167,7 +158,7 @@ message SetSockOptRequest { int32 sockfd = 1; int32 level = 2; int32 optname = 3; - bytes optval = 4; + SockOptVal optval = 4; } message SetSockOptResponse { @@ -175,30 +166,6 @@ message SetSockOptResponse { int32 errno_ = 2; // "errno" may fail to compile in c++. } -message SetSockOptIntRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - int32 intval = 4; -} - -message SetSockOptIntResponse { - int32 ret = 1; - int32 errno_ = 2; -} - -message SetSockOptTimevalRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - Timeval timeval = 4; -} - -message SetSockOptTimevalResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - message SocketRequest { int32 domain = 1; int32 type = 2; @@ -233,32 +200,16 @@ service Posix { rpc Connect(ConnectRequest) returns (ConnectResponse); // Call getsockname() on the DUT. rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse); - // Call getsockopt() on the DUT. You should prefer one of the other - // GetSockOpt* functions with a more structured optval or else you may get the - // encoding wrong, such as making a bad assumption about the server's word - // sizes or endianness. + // Call getsockopt() on the DUT. rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse); - // Call getsockopt() on the DUT with an int optval. - rpc GetSockOptInt(GetSockOptIntRequest) returns (GetSockOptIntResponse); - // Call getsockopt() on the DUT with a Timeval optval. - rpc GetSockOptTimeval(GetSockOptTimevalRequest) - returns (GetSockOptTimevalResponse); // Call listen() on the DUT. rpc Listen(ListenRequest) returns (ListenResponse); // Call send() on the DUT. rpc Send(SendRequest) returns (SendResponse); // Call sendto() on the DUT. rpc SendTo(SendToRequest) returns (SendToResponse); - // Call setsockopt() on the DUT. You should prefer one of the other - // SetSockOpt* functions with a more structured optval or else you may get the - // encoding wrong, such as making a bad assumption about the server's word - // sizes or endianness. + // Call setsockopt() on the DUT. rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse); - // Call setsockopt() on the DUT with an int optval. - rpc SetSockOptInt(SetSockOptIntRequest) returns (SetSockOptIntResponse); - // Call setsockopt() on the DUT with a Timeval optval. - rpc SetSockOptTimeval(SetSockOptTimevalRequest) - returns (SetSockOptTimevalResponse); // Call socket() on the DUT. rpc Socket(SocketRequest) returns (SocketResponse); // Call recv() on the DUT. diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 87eeeeb88..f68d9d62b 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -291,6 +291,26 @@ func (dut *DUT) GetSockNameWithErrno(ctx context.Context, sockfd int32) (int32, return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_()) } +func (dut *DUT) getSockOpt(ctx context.Context, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) { + dut.t.Helper() + req := pb.GetSockOptRequest{ + Sockfd: sockfd, + Level: level, + Optname: optname, + Optlen: optlen, + Type: typ, + } + resp, err := dut.posixServer.GetSockOpt(ctx, &req) + if err != nil { + dut.t.Fatalf("failed to call GetSockOpt: %s", err) + } + optval := resp.GetOptval() + if optval == nil { + dut.t.Fatalf("GetSockOpt response does not contain a value") + } + return resp.GetRet(), optval, syscall.Errno(resp.GetErrno_()) +} + // GetSockOpt calls getsockopt 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 GetSockOptWithErrno. Because endianess and the width of values @@ -312,17 +332,12 @@ func (dut *DUT) GetSockOpt(sockfd, level, optname, optlen int32) []byte { // prefer to use a more specific GetSockOptXxxWithErrno function. func (dut *DUT) GetSockOptWithErrno(ctx context.Context, sockfd, level, optname, optlen int32) (int32, []byte, error) { dut.t.Helper() - req := pb.GetSockOptRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Optlen: optlen, - } - resp, err := dut.posixServer.GetSockOpt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call GetSockOpt: %s", err) + ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES) + bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval) + if !ok { + dut.t.Fatalf("GetSockOpt got value type: %T, want bytes", optval) } - return resp.GetRet(), resp.GetOptval(), syscall.Errno(resp.GetErrno_()) + return ret, bytesval.Bytesval, errno } // GetSockOptInt calls getsockopt on the DUT and causes a fatal test failure @@ -342,16 +357,12 @@ func (dut *DUT) GetSockOptInt(sockfd, level, optname int32) int32 { // GetSockOptIntWithErrno calls getsockopt with an integer optval. func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, int32, error) { dut.t.Helper() - req := pb.GetSockOptIntRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - } - resp, err := dut.posixServer.GetSockOptInt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call GetSockOptInt: %s", err) + ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_INT) + intval, ok := optval.Val.(*pb.SockOptVal_Intval) + if !ok { + dut.t.Fatalf("GetSockOpt got value type: %T, want int", optval) } - return resp.GetRet(), resp.GetIntval(), syscall.Errno(resp.GetErrno_()) + return ret, intval.Intval, errno } // GetSockOptTimeval calls getsockopt on the DUT and causes a fatal test failure @@ -371,20 +382,16 @@ func (dut *DUT) GetSockOptTimeval(sockfd, level, optname int32) unix.Timeval { // GetSockOptTimevalWithErrno calls getsockopt and returns a timeval. func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, unix.Timeval, error) { dut.t.Helper() - req := pb.GetSockOptTimevalRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - } - resp, err := dut.posixServer.GetSockOptTimeval(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call GetSockOptTimeval: %s", err) + ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME) + tv, ok := optval.Val.(*pb.SockOptVal_Timeval) + if !ok { + dut.t.Fatalf("GetSockOpt got value type: %T, want timeval", optval) } timeval := unix.Timeval{ - Sec: resp.GetTimeval().Seconds, - Usec: resp.GetTimeval().Microseconds, + Sec: tv.Timeval.Seconds, + Usec: tv.Timeval.Microseconds, } - return resp.GetRet(), timeval, syscall.Errno(resp.GetErrno_()) + return ret, timeval, errno } // Listen calls listen on the DUT and causes a fatal test failure if it doesn't @@ -473,6 +480,21 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, sockfd int32, buf []byte, f return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } +func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) { + dut.t.Helper() + req := pb.SetSockOptRequest{ + Sockfd: sockfd, + Level: level, + Optname: optname, + Optval: optval, + } + resp, err := dut.posixServer.SetSockOpt(ctx, &req) + if err != nil { + dut.t.Fatalf("failed to call SetSockOpt: %s", err) + } + return resp.GetRet(), syscall.Errno(resp.GetErrno_()) +} + // SetSockOpt calls setsockopt 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 SetSockOptWithErrno. Because endianess and the width of values @@ -493,17 +515,7 @@ func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) { // prefer to use a more specific SetSockOptXxxWithErrno function. func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname int32, optval []byte) (int32, error) { dut.t.Helper() - req := pb.SetSockOptRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Optval: optval, - } - resp, err := dut.posixServer.SetSockOpt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOpt: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}}) } // SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure @@ -522,17 +534,7 @@ func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) { // SetSockOptIntWithErrno calls setsockopt with an integer optval. func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname, optval int32) (int32, error) { dut.t.Helper() - req := pb.SetSockOptIntRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Intval: optval, - } - resp, err := dut.posixServer.SetSockOptInt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOptInt: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}}) } // SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure @@ -556,17 +558,7 @@ func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, o Seconds: int64(tv.Sec), Microseconds: int64(tv.Usec), } - req := pb.SetSockOptTimevalRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Timeval: &timeval, - } - resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}}) } // Socket calls socket on the DUT and returns the file descriptor. If socket diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 6beccbfd0..e4ced444b 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -44,8 +44,36 @@ packetimpact_go_test( packetimpact_go_test( name = "tcp_window_shrink", srcs = ["tcp_window_shrink_test.go"], - # TODO(b/153202472): Fix netstack then remove the line below. - netstack = False, + deps = [ + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_go_test( + name = "tcp_zero_window_probe", + srcs = ["tcp_zero_window_probe_test.go"], + deps = [ + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_go_test( + name = "tcp_zero_window_probe_retransmit", + srcs = ["tcp_zero_window_probe_retransmit_test.go"], + deps = [ + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_go_test( + name = "tcp_zero_window_probe_usertimeout", + srcs = ["tcp_zero_window_probe_usertimeout_test.go"], deps = [ "//pkg/tcpip/header", "//test/packetimpact/testbench", diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go new file mode 100644 index 000000000..864e5a634 --- /dev/null +++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go @@ -0,0 +1,99 @@ +// 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_zero_window_probe_retransmit_test + +import ( + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + tb "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +// TestZeroWindowProbeRetransmit tests retransmits of zero window probes +// to be sent at exponentially inreasing time intervals. +func TestZeroWindowProbeRetransmit(t *testing.T) { + dut := tb.NewDUT(t) + defer dut.TearDown() + listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(listenFd) + conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) + defer conn.Close() + + conn.Handshake() + acceptFd, _ := dut.Accept(listenFd) + defer dut.Close(acceptFd) + + dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) + + sampleData := []byte("Sample Data") + samplePayload := &tb.Payload{Bytes: sampleData} + + // Send and receive sample data to the dut. + dut.Send(acceptFd, sampleData, 0) + if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil { + t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + } + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with sequence number %s", err) + } + + // Check for the dut to keep the connection alive as long as the zero window + // probes are acknowledged. Check if the zero window probes are sent at + // exponentially increasing intervals. The timeout intervals are function + // of the recorded first zero probe transmission duration. + // + // Advertize zero receive window again. + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) + probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1)) + ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum())) + + startProbeDuration := time.Second + current := startProbeDuration + first := time.Now() + // Ask the dut to send out data. + dut.Send(acceptFd, sampleData, 0) + // 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++ { + 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(&tb.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil { + t.Fatalf("expected a probe with sequence number %v: loop %d", probeSeq, i) + } + if i == 0 { + startProbeDuration = time.Now().Sub(first) + current = 2 * startProbeDuration + continue + } + // Check if the probes came at exponentially increasing intervals. + if p := time.Since(start); p < current-startProbeDuration { + t.Fatalf("zero probe came sooner interval %d probe %d\n", p, i) + } + // Acknowledge the zero-window probes from the dut. + conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) + current *= 2 + } + // Advertize non-zero window. + conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)}) + // Expect the dut to recover and transmit data. + if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { + t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + } +} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go new file mode 100644 index 000000000..4fa3d0cd4 --- /dev/null +++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go @@ -0,0 +1,107 @@ +// 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_zero_window_probe_test + +import ( + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + tb "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +// TestZeroWindowProbe tests few cases of zero window probing over the +// same connection. +func TestZeroWindowProbe(t *testing.T) { + dut := tb.NewDUT(t) + defer dut.TearDown() + listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(listenFd) + conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) + defer conn.Close() + + conn.Handshake() + acceptFd, _ := dut.Accept(listenFd) + defer dut.Close(acceptFd) + + dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) + + sampleData := []byte("Sample Data") + samplePayload := &tb.Payload{Bytes: sampleData} + + start := time.Now() + // Send and receive sample data to the dut. + dut.Send(acceptFd, sampleData, 0) + if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil { + t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + } + sendTime := time.Now().Sub(start) + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with sequence number %s", err) + } + + // Test 1: Check for receive of a zero window probe, record the duration for + // probe to be sent. + // + // Advertize zero window to the dut. + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) + + // Expected sequence number of the zero window probe. + probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1)) + // Expected ack number of the ACK for the probe. + ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum())) + + // Expect there are no zero-window probes sent until there is data to be sent out + // from the dut. + if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil { + t.Fatalf("unexpected a packet with sequence number %v: %s", probeSeq, err) + } + + start = time.Now() + // Ask the dut to send out data. + dut.Send(acceptFd, sampleData, 0) + // Expect zero-window probe from the dut. + if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err) + } + // Expect the probe to be sent after some time. Compare against the previous + // time recorded when the dut immediately sends out data on receiving the + // send command. + if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime { + t.Fatalf("expected the first probe to be sent out after retransmission interval, got %v want > %v\n", startProbeDuration, sendTime) + } + + // Test 2: Check if the dut recovers on advertizing non-zero receive window. + // and sends out the sample payload after the send window opens. + // + // Advertize non-zero window to the dut and ack the zero window probe. + conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)}) + // Expect the dut to recover and transmit data. + if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { + t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + } + + // Test 3: Sanity check for dut's processing of a similar probe it sent. + // Check if the dut responds as we do for a similar probe sent to it. + // Basically with sequence number to one byte behind the unacknowledged + // sequence number. + p := tb.Uint32(uint32(*conn.LocalSeqNum())) + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), SeqNum: tb.Uint32(uint32(*conn.LocalSeqNum() - 1))}) + if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with ack number: %d: %s", p, err) + } +} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go new file mode 100644 index 000000000..7d81c276c --- /dev/null +++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go @@ -0,0 +1,93 @@ +// 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_zero_window_probe_usertimeout_test + +import ( + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + tb "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are +// retransmitting zero window probes. +func TestZeroWindowProbeUserTimeout(t *testing.T) { + dut := tb.NewDUT(t) + defer dut.TearDown() + listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(listenFd) + conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) + defer conn.Close() + + conn.Handshake() + acceptFd, _ := dut.Accept(listenFd) + defer dut.Close(acceptFd) + + dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) + + sampleData := []byte("Sample Data") + samplePayload := &tb.Payload{Bytes: sampleData} + + // Send and receive sample data to the dut. + dut.Send(acceptFd, sampleData, 0) + if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil { + t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + } + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with sequence number %s", err) + } + + // Test 1: Check for receive of a zero window probe, record the duration for + // probe to be sent. + // + // Advertize zero window to the dut. + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) + + // Expected sequence number of the zero window probe. + probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1)) + start := time.Now() + // Ask the dut to send out data. + dut.Send(acceptFd, sampleData, 0) + // Expect zero-window probe from the dut. + if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { + t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err) + } + // Record the duration for first probe, the dut sends the zero window probe after + // a retransmission time interval. + startProbeDuration := time.Now().Sub(start) + + // Test 2: Check if the dut times out the connection by honoring usertimeout + // when the dut is sending zero-window probes. + // + // Reduce the retransmit timeout. + dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds())) + // Advertize zero window again. + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) + // Ask the dut to send out data that would trigger zero window probe retransmissions. + dut.Send(acceptFd, sampleData, 0) + + // Wait for the connection to timeout after multiple zero-window probe retransmissions. + time.Sleep(8 * startProbeDuration) + + // Expect the connection to have timed out and closed which would cause the dut + // to reply with a RST to the ACK we send. + conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) + if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + t.Fatalf("expected a TCP RST") + } +} |