Test TCP sender behavior against window shrinking

RFC 1122 Section 3.7: A sending TCP MUST be robust against window shrinking,
which may cause the "useable window" to become negative.

PiperOrigin-RevId: 305377072
This commit is contained in:
gVisor bot 2020-04-07 17:48:06 -07:00
parent acf0259255
commit dbcc59af0b
6 changed files with 220 additions and 7 deletions

View File

@ -149,6 +149,15 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
::grpc::Status Send(::grpc::ServerContext *context,
const ::posix_server::SendRequest *request,
::posix_server::SendResponse *response) override {
response->set_ret(::send(request->sockfd(), request->buf().data(),
request->buf().size(), request->flags()));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
::grpc::Status SetSockOpt(
grpc_impl::ServerContext *context,
const ::posix_server::SetSockOptRequest *request,
@ -160,6 +169,17 @@ class PosixImpl final : public posix_server::Posix::Service {
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,

View File

@ -93,6 +93,17 @@ message ListenResponse {
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
message SendRequest {
int32 sockfd = 1;
bytes buf = 2;
int32 flags = 3;
}
message SendResponse {
int32 ret = 1;
int32 errno_ = 2;
}
message SetSockOptRequest {
int32 sockfd = 1;
int32 level = 2;
@ -105,6 +116,18 @@ 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;
@ -151,11 +174,15 @@ service Posix {
rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
// Call listen() on the DUT.
rpc Listen(ListenRequest) returns (ListenResponse);
// Call send() on the DUT.
rpc Send(SendRequest) returns (SendResponse);
// 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.
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);

View File

@ -187,9 +187,19 @@ func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
conn.SendFrame(conn.CreateFrame(tcp, additionalLayers...))
}
// Recv gets a packet from the sniffer within the timeout provided. If no packet
// arrives before the timeout, it returns nil.
// Recv gets a packet from the sniffer within the timeout provided.
// If no packet arrives before the timeout, it returns nil.
func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
layers := conn.RecvFrame(timeout)
if tcpLayerIndex < len(layers) {
return layers[tcpLayerIndex].(*TCP)
}
return nil
}
// RecvFrame gets a frame (of type Layers) within the timeout provided.
// If no frame arrives before the timeout, it returns nil.
func (conn *TCPIPv4) RecvFrame(timeout time.Duration) Layers {
deadline := time.Now().Add(timeout)
for {
timeout = time.Until(deadline)
@ -216,14 +226,16 @@ func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
for i := tcpLayerIndex + 1; i < len(layers); i++ {
conn.RemoteSeqNum.UpdateForward(seqnum.Size(layers[i].length()))
}
return tcpHeader
return layers
}
return nil
}
// Expect a packet that matches the provided tcp within the timeout specified.
// If it doesn't arrive in time, the test fails.
// If it doesn't arrive in time, it returns nil.
func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) *TCP {
// We cannot implement this directly using ExpectFrame as we cannot specify
// the Payload part.
deadline := time.Now().Add(timeout)
for {
timeout = time.Until(deadline)
@ -231,15 +243,41 @@ func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) *TCP {
return nil
}
gotTCP := conn.Recv(timeout)
if gotTCP == nil {
return nil
}
if tcp.match(gotTCP) {
return gotTCP
}
}
}
// ExpectFrame expects a frame that matches the specified layers within the
// timeout specified. If it doesn't arrive in time, it returns nil.
func (conn *TCPIPv4) ExpectFrame(layers Layers, timeout time.Duration) Layers {
deadline := time.Now().Add(timeout)
for {
timeout = time.Until(deadline)
if timeout <= 0 {
return nil
}
gotLayers := conn.RecvFrame(timeout)
if layers.match(gotLayers) {
return gotLayers
}
}
}
// ExpectData is a convenient method that expects a TCP packet along with
// the payload to arrive within the timeout specified. If it doesn't arrive
// in time, it causes a fatal test failure.
func (conn *TCPIPv4) ExpectData(tcp TCP, data []byte, timeout time.Duration) {
expected := []Layer{&Ether{}, &IPv4{}, &tcp}
if len(data) > 0 {
expected = append(expected, &Payload{Bytes: data})
}
if conn.ExpectFrame(expected, timeout) == nil {
conn.t.Fatalf("expected to get a TCP frame %s with payload %x", &tcp, data)
}
}
// Handshake performs a TCP 3-way handshake.
func (conn *TCPIPv4) Handshake() {
// Send the SYN.

View File

@ -291,6 +291,35 @@ func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
// Send calls send 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
// SendWithErrno.
func (dut *DUT) Send(sockfd int32, buf []byte, flags int32) int32 {
dut.t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
defer cancel()
ret, err := dut.SendWithErrno(ctx, sockfd, buf, flags)
if ret == -1 {
dut.t.Fatalf("failed to send: %s", err)
}
return ret
}
// SendWithErrno calls send on the DUT.
func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32) (int32, error) {
dut.t.Helper()
req := pb.SendRequest{
Sockfd: sockfd,
Buf: buf,
Flags: flags,
}
resp, err := dut.posixServer.Send(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call Send: %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
@ -324,6 +353,35 @@ func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure
// if it doesn't succeed. If more control over the int optval or error handling
// is needed, use SetSockOptIntWithErrno.
func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) {
dut.t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
defer cancel()
ret, err := dut.SetSockOptIntWithErrno(ctx, sockfd, level, optname, optval)
if ret != 0 {
dut.t.Fatalf("failed to SetSockOptInt: %s", err)
}
}
// 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_())
}
// SetSockOptTimeval 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 SetSockOptTimevalWithErrno.

View File

@ -28,6 +28,18 @@ 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",
],
)
sh_binary(
name = "test_runner",
srcs = ["test_runner.sh"],

View File

@ -0,0 +1,58 @@
// 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_window_shrink_test
import (
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
tb "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func TestWindowShrink(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")
dut.Send(acceptFd, sampleData, 0)
conn.ExpectData(tb.TCP{}, sampleData, time.Second)
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
dut.Send(acceptFd, sampleData, 0)
dut.Send(acceptFd, sampleData, 0)
conn.ExpectData(tb.TCP{}, sampleData, time.Second)
conn.ExpectData(tb.TCP{}, sampleData, time.Second)
// We close our receiving window here
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
dut.Send(acceptFd, []byte("Sample Data"), 0)
// Note: There is another kind of zero-window probing which Windows uses (by sending one
// new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change
// the following lines.
conn.ExpectData(tb.TCP{SeqNum: tb.Uint32(uint32(conn.RemoteSeqNum - 1))}, nil, time.Second)
}