mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Test TCP should queue RECEIVE request in SYN-SENT
PiperOrigin-RevId: 313878910
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
Reference in New Issue
Block a user