Enqueue TCP sends arriving in SYN_SENT state.

TCP needs to enqueue any send requests arriving when the connection is in
SYN_SENT state. The data should be sent out soon after completion of the
connection handshake.

Fixes #3995

PiperOrigin-RevId: 332482041
This commit is contained in:
Mithun Iyer
2020-09-18 10:49:21 -07:00
committed by gVisor bot
parent 93fd164fa2
commit fcf8d7c6dd
3 changed files with 154 additions and 8 deletions
+11 -8
View File
@@ -1317,14 +1317,17 @@ func (e *endpoint) readLocked() (buffer.View, *tcpip.Error) {
// indicating the reason why it's not writable.
// Caller must hold e.mu and e.sndBufMu
func (e *endpoint) isEndpointWritableLocked() (int, *tcpip.Error) {
// The endpoint cannot be written to if it's not connected.
if !e.EndpointState().connected() {
switch e.EndpointState() {
case StateError:
return 0, e.HardError
default:
return 0, tcpip.ErrClosedForSend
}
switch s := e.EndpointState(); {
case s == StateError:
return 0, e.HardError
case !s.connecting() && !s.connected():
return 0, tcpip.ErrClosedForSend
case s.connecting():
// As per RFC793, page 56, a send request arriving when in connecting
// state, can be queued to be completed after the state becomes
// connected. Return an error code for the caller of endpoint Write to
// try again, until the connection handshake is complete.
return 0, tcpip.ErrWouldBlock
}
// Check if the connection has already been closed for sends.
+10
View File
@@ -271,6 +271,16 @@ packetimpact_go_test(
],
)
packetimpact_go_test(
name = "tcp_queue_send_in_syn_sent",
srcs = ["tcp_queue_send_in_syn_sent_test.go"],
deps = [
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
],
)
packetimpact_go_test(
name = "icmpv6_param_problem",
srcs = ["icmpv6_param_problem_test.go"],
@@ -0,0 +1,133 @@
// 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_send_in_syn_sent_test
import (
"context"
"errors"
"flag"
"net"
"sync"
"syscall"
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
testbench.RegisterFlags(flag.CommandLine)
}
// TestQueueSendInSynSent tests send behavior when the TCP state
// is SYN-SENT.
// It tests for 2 variants when in SYN_SENT state and:
// (1) DUT blocks on send and complete handshake
// (2) DUT blocks on send and receive a TCP RST.
func TestQueueSendInSynSent(t *testing.T) {
for _, tt := range []struct {
description string
reset bool
}{
{description: "Complete handshake", 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(t, 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(t)
sampleData := []byte("Sample Data")
samplePayload := &testbench.Payload{Bytes: sampleData}
dut.SetNonBlocking(t, socket, true)
if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) {
t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
}
if _, err := conn.Expect(t, 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.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != syscall.Errno(unix.EWOULDBLOCK) {
t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err)
}
// Test blocking write.
dut.SetNonBlocking(t, 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 SEND call in SYN-SENT, this should be queued for
// process until the connection is established.
n, err := dut.SendWithErrno(ctx, t, socket, 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 != int32(len(sampleData)) {
t.Errorf("failed to send on DUT: %s", err)
}
}()
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint send.
block.Wait()
// The following sleep is used to prevent the connection
// from being established before we are blocked on send.
time.Sleep(100 * time.Millisecond)
if tt.reset {
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)})
return
}
// Bring the connection to Established.
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)})
// Expect the data from the DUT's enqueued send request.
//
// On Linux, this can be piggybacked with the ACK completing the
// handshake. On gVisor, getting such a piggyback is a bit more
// complicated because the actual data enqueuing occurs in the
// callers of endpoint Write.
if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, samplePayload, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s", err)
}
// Send sample payload and expect an ACK to ensure connection is still ESTABLISHED.
conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK from DUT, but got none: %s", err)
}
})
}
}