Test TCP should queue RECEIVE request in SYN-SENT

PiperOrigin-RevId: 313878910
This commit is contained in:
Zeling Feng
2020-05-29 17:24:20 -07:00
committed by gVisor bot
parent 93edb36cbb
commit a9b47390c8
6 changed files with 175 additions and 7 deletions
+8
View File
@@ -158,6 +158,14 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
::grpc::Status Fcntl(grpc_impl::ServerContext *context,
const ::posix_server::FcntlRequest *request,
::posix_server::FcntlResponse *response) override {
response->set_ret(::fcntl(request->fd(), request->cmd(), request->arg()));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
::grpc::Status GetSockName(
grpc_impl::ServerContext *context,
const ::posix_server::GetSockNameRequest *request,
@@ -91,6 +91,17 @@ 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;
}
@@ -198,6 +209,8 @@ 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.
+13 -7
View File
@@ -266,14 +266,14 @@ func SeqNumValue(v seqnum.Value) *seqnum.Value {
}
// newTCPState creates a new TCPState.
func newTCPState(domain int, out, in TCP) (*tcpState, error) {
func newTCPState(domain int, out, in TCP) (*tcpState, unix.Sockaddr, error) {
portPickerFD, localAddr, err := pickPort(domain, unix.SOCK_STREAM)
if err != nil {
return nil, err
return nil, nil, err
}
localPort, err := portFromSockaddr(localAddr)
if err != nil {
return nil, err
return nil, nil, err
}
s := tcpState{
out: TCP{SrcPort: &localPort},
@@ -283,12 +283,12 @@ func newTCPState(domain int, out, in TCP) (*tcpState, error) {
finSent: false,
}
if err := s.out.merge(&out); err != nil {
return nil, err
return nil, nil, err
}
if err := s.in.merge(&in); err != nil {
return nil, err
return nil, nil, err
}
return &s, nil
return &s, localAddr, nil
}
func (s *tcpState) outgoing() Layer {
@@ -606,7 +606,7 @@ func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
if err != nil {
t.Fatalf("can't make ipv4State: %s", err)
}
tcpState, err := newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
tcpState, localAddr, err := newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
if err != nil {
t.Fatalf("can't make tcpState: %s", err)
}
@@ -623,6 +623,7 @@ func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
layerStates: []layerState{etherState, ipv4State, tcpState},
injector: injector,
sniffer: sniffer,
localAddr: localAddr,
t: t,
}
}
@@ -703,6 +704,11 @@ func (conn *TCPIPv4) SynAck() *TCP {
return conn.state().synAck
}
// LocalAddr gets the local socket address of this connection.
func (conn *TCPIPv4) LocalAddr() unix.Sockaddr {
return conn.localAddr
}
// IPv6Conn maintains the state for all the layers in a IPv6 connection.
type IPv6Conn Connection
+42
View File
@@ -262,6 +262,35 @@ func (dut *DUT) ConnectWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr
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(fd, cmd, arg int32) int32 {
dut.t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
ret, err := dut.FcntlWithErrno(ctx, fd, cmd, arg)
if ret == -1 {
dut.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, fd, cmd, arg int32) (int32, error) {
dut.t.Helper()
req := pb.FcntlRequest{
Fd: fd,
Cmd: cmd,
Arg: arg,
}
resp, err := dut.posixServer.Fcntl(ctx, &req)
if err != nil {
dut.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.
@@ -478,6 +507,19 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, sockfd int32, buf []byte, f
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
// SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking
// is true, otherwise it will clear the flag.
func (dut *DUT) SetNonBlocking(fd int32, nonblocking bool) {
dut.t.Helper()
flags := dut.Fcntl(fd, unix.F_GETFL, 0)
if nonblocking {
flags |= unix.O_NONBLOCK
} else {
flags &= ^unix.O_NONBLOCK
}
dut.Fcntl(fd, unix.F_SETFL, flags)
}
func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) {
dut.t.Helper()
req := pb.SetSockOptRequest{
+12
View File
@@ -169,6 +169,18 @@ packetimpact_go_test(
],
)
packetimpact_go_test(
name = "tcp_queue_receive_in_syn_sent",
srcs = ["tcp_queue_receive_in_syn_sent_test.go"],
# TODO(b/157658105): Fix netstack then remove the line below.
expect_netstack_failure = True,
deps = [
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
],
)
packetimpact_go_test(
name = "tcp_synsent_reset",
srcs = ["tcp_synsent_reset_test.go"],
@@ -0,0 +1,87 @@
// 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"
"net"
"sync"
"syscall"
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
tb "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
tb.RegisterFlags(flag.CommandLine)
}
func TestQueueReceiveInSynSent(t *testing.T) {
dut := tb.NewDUT(t)
defer dut.TearDown()
socket, remotePort := dut.CreateBoundSocket(unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(tb.RemoteIPv4))
conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
defer conn.Close()
sampleData := []byte("Sample Data")
dut.SetNonBlocking(socket, true)
if _, err := dut.ConnectWithErrno(context.Background(), socket, conn.LocalAddr()); !errors.Is(err, syscall.EINPROGRESS) {
t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
}
if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn)}, time.Second); err != nil {
t.Fatalf("expected a SYN from DUT, but got none: %s", err)
}
// Issue RECEIVE call in SYN-SENT, this should be queued for process until the connection
// is established.
dut.SetNonBlocking(socket, false)
var wg sync.WaitGroup
defer wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
n, buff, err := dut.RecvWithErrno(ctx, socket, int32(len(sampleData)), 0)
if n == -1 {
t.Fatalf("failed to recv on DUT: %s", err)
}
if got := buff[:n]; !bytes.Equal(got, sampleData) {
t.Fatalf("received data don't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData))
}
}()
// The following sleep is used to prevent the connection from being established while the
// RPC is in flight.
time.Sleep(time.Second)
// Bring the connection to Established.
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn | header.TCPFlagAck)})
if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK from DUT, but got none: %s", err)
}
// Send sample data to DUT.
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, &tb.Payload{Bytes: sampleData})
}