TCP receive should block when in SYN-SENT state.

The application can choose to initiate a non-blocking connect and
later block on a read, when the endpoint is still in SYN-SENT state.

PiperOrigin-RevId: 319311016
This commit is contained in:
Mithun Iyer
2020-07-01 15:47:50 -07:00
committed by gVisor bot
parent e4b2087602
commit 31b27adf9b
3 changed files with 99 additions and 48 deletions
+10 -2
View File
@@ -1212,6 +1212,16 @@ func (e *endpoint) SetOwner(owner tcpip.PacketOwner) {
// Read reads data from the endpoint.
func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
// When in SYN-SENT state, let the caller block on the receive.
// An application can initiate a non-blocking connect and then block
// on a receive. It can expect to read any data after the handshake
// is complete. RFC793, section 3.9, p58.
if e.EndpointState() == StateSynSent {
return buffer.View{}, tcpip.ControlMessages{}, tcpip.ErrWouldBlock
}
// The endpoint can be read if it's connected, or if it's already closed
// but has some pending unread data. Also note that a RST being received
// would cause the state to become StateError so we should allow the
@@ -1221,7 +1231,6 @@ func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages,
if s := e.EndpointState(); !s.connected() && s != StateClose && bufUsed == 0 {
e.rcvListMu.Unlock()
he := e.HardError
e.UnlockUser()
if s == StateError {
return buffer.View{}, tcpip.ControlMessages{}, he
}
@@ -1231,7 +1240,6 @@ func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages,
v, err := e.readLocked()
e.rcvListMu.Unlock()
e.UnlockUser()
if err == tcpip.ErrClosedForReceive {
e.stats.ReadErrors.ReadClosed.Increment()
-2
View File
@@ -183,8 +183,6 @@ 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",
@@ -35,53 +35,98 @@ func init() {
testbench.RegisterFlags(flag.CommandLine)
}
// TestQueueReceiveInSynSent tests receive behavior when the TCP state
// is SYN-SENT.
// It tests for 2 variants where the receive is blocked and:
// (1) we complete handshake and send sample data.
// (2) we send a TCP RST.
func TestQueueReceiveInSynSent(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
for _, tt := range []struct {
description string
reset bool
}{
{description: "Send DATA", reset: false},
{description: "Send RST", reset: true},
} {
t.Run(tt.description, func(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
socket, remotePort := dut.CreateBoundSocket(unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4))
conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close()
socket, remotePort := dut.CreateBoundSocket(unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4))
conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close()
sampleData := []byte("Sample Data")
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)
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(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil {
t.Fatalf("expected a SYN from DUT, but got none: %s", err)
}
if _, _, err := dut.RecvWithErrno(context.Background(), socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) {
t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err)
}
// Test blocking read.
dut.SetNonBlocking(socket, false)
var wg sync.WaitGroup
defer wg.Wait()
wg.Add(1)
var block sync.WaitGroup
block.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
block.Done()
// Issue RECEIVE call in SYN-SENT, this should be queued for
// process until the connection is established.
n, buff, err := dut.RecvWithErrno(ctx, socket, int32(len(sampleData)), 0)
if tt.reset {
if err != syscall.Errno(unix.ECONNREFUSED) {
t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err)
}
if n != -1 {
t.Errorf("expected return value %d, got %d", -1, n)
}
return
}
if n == -1 {
t.Errorf("failed to recv on DUT: %s", err)
}
if got := buff[:n]; !bytes.Equal(got, sampleData) {
t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData))
}
}()
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint receive.
block.Wait()
// The following sleep is used to prevent the connection
// from being established before we are blocked on Recv.
time.Sleep(100 * time.Millisecond)
if tt.reset {
conn.Send(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)})
return
}
// Bring the connection to Established.
conn.Send(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)})
if _, err := conn.Expect(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK from DUT, but got none: %s", err)
}
// Send sample payload and expect an ACK.
conn.Send(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
if _, err := conn.Expect(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK from DUT, but got none: %s", err)
}
})
}
if _, err := conn.Expect(testbench.TCP{Flags: testbench.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(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)})
if _, err := conn.Expect(testbench.TCP{Flags: testbench.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(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
}