mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user