TCP to honor updated window size during handshake.

In passive open cases, we transition to Established state after
initializing endpoint's sender and receiver. With this we lose out
on any updates coming from the ACK that completes the handshake.
This change ensures that we uniformly transition to Established in all
cases and does minor cleanups.

Fixes #2938

PiperOrigin-RevId: 316567014
This commit is contained in:
Mithun Iyer
2020-06-15 16:19:53 -07:00
committed by gVisor bot
parent 885605c5e4
commit 67f261a87d
4 changed files with 107 additions and 29 deletions
+16 -12
View File
@@ -222,12 +222,6 @@ func (l *listenContext) createConnectingEndpoint(s *segment, iss seqnum.Value, i
n.initGSO()
// Create sender and receiver.
//
// The receiver at least temporarily has a zero receive window scale,
// but the caller may change it (before starting the protocol loop).
n.snd = newSender(n, iss, irs, s.window, rcvdSynOpts.MSS, rcvdSynOpts.WS)
n.rcv = newReceiver(n, irs, seqnum.Size(n.initialReceiveWindow()), 0, seqnum.Size(n.receiveBufferSize()))
// Bootstrap the auto tuning algorithm. Starting at zero will result in
// a large step function on the first window adjustment causing the
// window to grow to a really large value.
@@ -295,7 +289,7 @@ func (l *listenContext) createEndpointAndPerformHandshake(s *segment, opts *head
}
// Perform the 3-way handshake.
h := newPassiveHandshake(ep, ep.rcv.rcvWnd, isn, irs, opts, deferAccept)
h := newPassiveHandshake(ep, seqnum.Size(ep.initialReceiveWindow()), isn, irs, opts, deferAccept)
if err := h.execute(); err != nil {
ep.mu.Unlock()
ep.Close()
@@ -536,6 +530,9 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
return
}
iss := s.ackNumber - 1
irs := s.sequenceNumber - 1
// Since SYN cookies are in use this is potentially an ACK to a
// SYN-ACK we sent but don't have a half open connection state
// as cookies are being used to protect against a potential SYN
@@ -546,7 +543,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
// when under a potential syn flood attack.
//
// Validate the cookie.
data, ok := ctx.isCookieValid(s.id, s.ackNumber-1, s.sequenceNumber-1)
data, ok := ctx.isCookieValid(s.id, iss, irs)
if !ok || int(data) >= len(mssTable) {
e.stack.Stats().TCP.ListenOverflowInvalidSynCookieRcvd.Increment()
e.stack.Stats().DroppedPackets.Increment()
@@ -571,7 +568,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
rcvdSynOptions.TSEcr = s.parsedOptions.TSEcr
}
n, err := ctx.createConnectingEndpoint(s, s.ackNumber-1, s.sequenceNumber-1, rcvdSynOptions, &waiter.Queue{})
n, err := ctx.createConnectingEndpoint(s, iss, irs, rcvdSynOptions, &waiter.Queue{})
if err != nil {
e.stack.Stats().TCP.FailedConnectionAttempts.Increment()
e.stats.FailedConnectionAttempts.Increment()
@@ -589,10 +586,17 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
n.tsOffset = 0
// Switch state to connected.
// We do not use transitionToStateEstablishedLocked here as there is
// no handshake state available when doing a SYN cookie based accept.
n.isConnectNotified = true
n.setEndpointState(StateEstablished)
n.transitionToStateEstablishedLocked(&handshake{
ep: n,
iss: iss,
ackNum: irs + 1,
rcvWnd: seqnum.Size(n.initialReceiveWindow()),
sndWnd: s.window,
rcvWndScale: e.rcvWndScaleForHandshake(),
sndWndScale: rcvdSynOptions.WS,
mss: rcvdSynOptions.MSS,
})
// Do the delivery in a separate goroutine so
// that we don't block the listen loop in case
+15 -17
View File
@@ -995,24 +995,22 @@ func (e *endpoint) completeWorkerLocked() {
// transitionToStateEstablisedLocked transitions a given endpoint
// to an established state using the handshake parameters provided.
// It also initializes sender/receiver if required.
// It also initializes sender/receiver.
func (e *endpoint) transitionToStateEstablishedLocked(h *handshake) {
if e.snd == nil {
// Transfer handshake state to TCP connection. We disable
// receive window scaling if the peer doesn't support it
// (indicated by a negative send window scale).
e.snd = newSender(e, h.iss, h.ackNum-1, h.sndWnd, h.mss, h.sndWndScale)
}
if e.rcv == nil {
rcvBufSize := seqnum.Size(e.receiveBufferSize())
e.rcvListMu.Lock()
e.rcv = newReceiver(e, h.ackNum-1, h.rcvWnd, h.effectiveRcvWndScale(), rcvBufSize)
// Bootstrap the auto tuning algorithm. Starting at zero will
// result in a really large receive window after the first auto
// tuning adjustment.
e.rcvAutoParams.prevCopied = int(h.rcvWnd)
e.rcvListMu.Unlock()
}
// Transfer handshake state to TCP connection. We disable
// receive window scaling if the peer doesn't support it
// (indicated by a negative send window scale).
e.snd = newSender(e, h.iss, h.ackNum-1, h.sndWnd, h.mss, h.sndWndScale)
rcvBufSize := seqnum.Size(e.receiveBufferSize())
e.rcvListMu.Lock()
e.rcv = newReceiver(e, h.ackNum-1, h.rcvWnd, h.effectiveRcvWndScale(), rcvBufSize)
// Bootstrap the auto tuning algorithm. Starting at zero will
// result in a really large receive window after the first auto
// tuning adjustment.
e.rcvAutoParams.prevCopied = int(h.rcvWnd)
e.rcvListMu.Unlock()
e.setEndpointState(StateEstablished)
}
+10
View File
@@ -232,6 +232,16 @@ packetimpact_go_test(
],
)
packetimpact_go_test(
name = "tcp_handshake_window_size",
srcs = ["tcp_handshake_window_size_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,66 @@
// 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_handshake_window_size_test
import (
"flag"
"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)
}
// TestTCPHandshakeWindowSize tests if the stack is honoring the window size
// communicated during handshake.
func TestTCPHandshakeWindowSize(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
defer dut.Close(listenFD)
conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close()
// Start handshake with zero window size.
conn.Send(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))})
if _, err := conn.ExpectData(&testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
t.Fatalf("expected SYN-ACK: %s", err)
}
// Update the advertised window size to a non-zero value with the ACK that
// completes the handshake.
//
// Set the window size with MSB set and expect the dut to treat it as
// an unsigned value.
conn.Send(testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))})
acceptFd, _ := dut.Accept(listenFD)
defer dut.Close(acceptFd)
sampleData := []byte("Sample Data")
samplePayload := &testbench.Payload{Bytes: sampleData}
// Since we advertised a zero window followed by a non-zero window,
// expect the dut to honor the recently advertised non-zero window
// and actually send out the data instead of probing for zero window.
dut.Send(acceptFd, sampleData, 0)
if _, err := conn.ExpectNextData(&testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil {
t.Fatalf("expected payload was not received: %s", err)
}
}