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:
parent
acf0259255
commit
dbcc59af0b
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue