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