summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorgVisor bot <gvisor-bot@google.com>2020-04-16 12:21:06 -0700
committergVisor bot <gvisor-bot@google.com>2020-04-16 12:22:17 -0700
commiteb7b1903e00eda9248da59991d80594590c9aab6 (patch)
tree6b749ffd48439907c3c084b90ceaed1227032819
parent28399818fc1e5d294cc93ddd4a1ac7e31c375fbf (diff)
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
-rw-r--r--test/packetimpact/testbench/connections.go14
-rw-r--r--test/packetimpact/tests/BUILD13
-rw-r--r--test/packetimpact/tests/tcp_close_wait_ack_test.go102
3 files changed, 126 insertions, 3 deletions
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index be62d051d..c1b3c4380 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -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
}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 4f8c8bdc0..690cee140 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -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"],
diff --git a/test/packetimpact/tests/tcp_close_wait_ack_test.go b/test/packetimpact/tests/tcp_close_wait_ack_test.go
new file mode 100644
index 000000000..eb4cc7a65
--- /dev/null
+++ b/test/packetimpact/tests/tcp_close_wait_ack_test.go
@@ -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)}
+}