Test TCP behavior when receiving unacceptable segment in CLOSE_WAIT

TCP, in CLOSE-WAIT state, MUST return ACK with proper SEQ and ACK numbers after
recv a seg with OTW SEQ or unacc ACK number, and remain in same state. If the
connection is in a synchronized state, any unacceptable segment (out of window
sequence number or unacceptable acknowledgment number) must elicit only an empty
acknowledgment segment containing the current send-sequence number and an
acknowledgment indicating the next sequence number expected to be received, and
the connection remains in the same state.

PiperOrigin-RevId: 306897984
This commit is contained in:
gVisor bot
2020-04-16 12:22:17 -07:00
parent 28399818fc
commit eb7b1903e0
3 changed files with 126 additions and 3 deletions
+11 -3
View File
@@ -189,6 +189,7 @@ type tcpState struct {
localSeqNum, remoteSeqNum *seqnum.Value
synAck *TCP
portPickerFD int
finSent bool
}
var _ layerState = (*tcpState)(nil)
@@ -210,6 +211,7 @@ func newTCPState(out, in TCP) (*tcpState, error) {
in: TCP{DstPort: &localPort},
localSeqNum: SeqNumValue(seqnum.Value(rand.Uint32())),
portPickerFD: portPickerFD,
finSent: false,
}
if err := s.out.merge(&out); err != nil {
return nil, err
@@ -254,12 +256,18 @@ func (s *tcpState) sent(sent Layer) error {
if !ok {
return fmt.Errorf("can't update tcpState with %T Layer", sent)
}
for current := tcp.next(); current != nil; current = current.next() {
s.localSeqNum.UpdateForward(seqnum.Size(current.length()))
if !s.finSent {
// update localSeqNum by the payload only when FIN is not yet sent by us
for current := tcp.next(); current != nil; current = current.next() {
s.localSeqNum.UpdateForward(seqnum.Size(current.length()))
}
}
if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 {
s.localSeqNum.UpdateForward(1)
}
if *tcp.Flags&(header.TCPFlagFin) != 0 {
s.finSent = true
}
return nil
}
@@ -590,7 +598,7 @@ func (conn *TCPIPv4) RemoteSeqNum() *seqnum.Value {
return conn.state().remoteSeqNum
}
// LocalSeqNum returns the next expected sequence number from the DUT.
// LocalSeqNum returns the next sequence number to send from the testbench.
func (conn *TCPIPv4) LocalSeqNum() *seqnum.Value {
return conn.state().localSeqNum
}
+13
View File
@@ -75,6 +75,19 @@ packetimpact_go_test(
],
)
packetimpact_go_test(
name = "tcp_close_wait_ack",
srcs = ["tcp_close_wait_ack_test.go"],
# TODO(b/153574037): Fix netstack then remove the line below.
netstack = False,
deps = [
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
],
)
sh_binary(
name = "test_runner",
srcs = ["test_runner.sh"],
@@ -0,0 +1,102 @@
// 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_close_wait_ack_test
import (
"fmt"
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
tb "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func TestCloseWaitAck(t *testing.T) {
for _, tt := range []struct {
description string
makeTestingTCP func(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP
seqNumOffset seqnum.Size
expectAck bool
}{
{"OTW", GenerateOTWSeqSegment, 0, false},
{"OTW", GenerateOTWSeqSegment, 1, true},
{"OTW", GenerateOTWSeqSegment, 2, true},
{"ACK", GenerateUnaccACKSegment, 0, false},
{"ACK", GenerateUnaccACKSegment, 1, true},
{"ACK", GenerateUnaccACKSegment, 2, true},
} {
t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
dut := tb.NewDUT(t)
defer dut.TearDown()
listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
defer dut.Close(listenFd)
conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
defer conn.Close()
conn.Handshake()
acceptFd, _ := dut.Accept(listenFd)
// Send a FIN to DUT to intiate the active close
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)})
if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err)
}
// Send a segment with OTW Seq / unacc ACK and expect an ACK back
conn.Send(tt.makeTestingTCP(&conn, tt.seqNumOffset), &tb.Payload{Bytes: []byte("Sample Data")})
gotAck, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
if tt.expectAck && err != nil {
t.Fatalf("expected an ack but got none: %s", err)
}
if !tt.expectAck && gotAck != nil {
t.Fatalf("expected no ack but got one: %s", gotAck)
}
// Now let's verify DUT is indeed in CLOSE_WAIT
dut.Close(acceptFd)
if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil {
t.Fatalf("expected DUT to send a FIN: %s", err)
}
// Ack the FIN from DUT
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
// Send some extra data to DUT
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, &tb.Payload{Bytes: []byte("Sample Data")})
if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
t.Fatalf("expected DUT to send an RST: %s", err)
}
})
}
}
// This generates an segment with seqnum = RCV.NXT + RCV.WND + seqNumOffset, the
// generated segment is only acceptable when seqNumOffset is 0, otherwise an ACK
// is expected from the receiver.
func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
windowSize := seqnum.Size(*conn.SynAck().WindowSize)
lastAcceptable := conn.LocalSeqNum().Add(windowSize - 1)
otwSeq := uint32(lastAcceptable.Add(seqNumOffset))
return tb.TCP{SeqNum: tb.Uint32(otwSeq), Flags: tb.Uint8(header.TCPFlagAck)}
}
// This generates an segment with acknum = SND.NXT + seqNumOffset, the generated
// segment is only acceptable when seqNumOffset is 0, otherwise an ACK is
// expected from the receiver.
func GenerateUnaccACKSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
lastAcceptable := conn.RemoteSeqNum()
unaccAck := uint32(lastAcceptable.Add(seqNumOffset))
return tb.TCP{AckNum: tb.Uint32(unaccAck), Flags: tb.Uint8(header.TCPFlagAck)}
}