summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact')
-rw-r--r--test/packetimpact/dut/posix_server.cc119
-rw-r--r--test/packetimpact/proto/posix_server.proto87
-rw-r--r--test/packetimpact/testbench/dut.go118
-rw-r--r--test/packetimpact/tests/BUILD32
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go99
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_test.go107
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go93
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")
+ }
+}