diff options
Diffstat (limited to 'test/packetimpact/tests')
39 files changed, 0 insertions, 6675 deletions
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD deleted file mode 100644 index 4cff0cf4c..000000000 --- a/test/packetimpact/tests/BUILD +++ /dev/null @@ -1,431 +0,0 @@ -load("//test/packetimpact/runner:defs.bzl", "ALL_TESTS", "packetimpact_go_test", "packetimpact_testbench", "validate_all_tests") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -packetimpact_testbench( - name = "fin_wait2_timeout", - srcs = ["fin_wait2_timeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv4_id_uniqueness", - srcs = ["ipv4_id_uniqueness_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_discard_mcast_source_addr", - srcs = ["udp_discard_mcast_source_addr_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_any_addr_recv_unicast", - srcs = ["udp_any_addr_recv_unicast_test.go"], - deps = [ - "//pkg/tcpip", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_icmp_error_propagation", - srcs = ["udp_icmp_error_propagation_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_window_shrink", - srcs = ["tcp_window_shrink_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe", - srcs = ["tcp_zero_window_probe_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe_retransmit", - srcs = ["tcp_zero_window_probe_retransmit_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe_usertimeout", - srcs = ["tcp_zero_window_probe_usertimeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_retransmits", - srcs = ["tcp_retransmits_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_outside_the_window", - srcs = ["tcp_outside_the_window_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_noaccept_close_rst", - srcs = ["tcp_noaccept_close_rst_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_send_window_sizes_piggyback", - srcs = ["tcp_send_window_sizes_piggyback_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_unacc_seq_ack", - srcs = ["tcp_unacc_seq_ack_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_paws_mechanism", - srcs = ["tcp_paws_mechanism_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_user_timeout", - srcs = ["tcp_user_timeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_queue_send_recv_in_syn_sent", - srcs = ["tcp_queue_send_recv_in_syn_sent_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_synsent_reset", - srcs = ["tcp_synsent_reset_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_synrcvd_reset", - srcs = ["tcp_synrcvd_reset_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_network_unreachable", - srcs = ["tcp_network_unreachable_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_cork_mss", - srcs = ["tcp_cork_mss_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - 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_testbench( - name = "tcp_timewait_reset", - srcs = ["tcp_timewait_reset_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "icmpv6_param_problem", - srcs = ["icmpv6_param_problem_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_unknown_options_action", - srcs = ["ipv6_unknown_options_action_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv4_fragment_reassembly", - srcs = ["ipv4_fragment_reassembly_test.go"], - deps = [ - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_fragment_reassembly", - srcs = ["ipv6_fragment_reassembly_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_fragment_icmp_error", - srcs = ["ipv6_fragment_icmp_error_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/network/ipv6", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_linger", - srcs = ["tcp_linger_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_rcv_buf_space", - srcs = ["tcp_rcv_buf_space_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_receive_window", - srcs = ["tcp_zero_receive_window_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_rack", - srcs = ["tcp_rack_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_info", - srcs = ["tcp_info_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_fin_retransmission", - srcs = ["tcp_fin_retransmission_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_listen_backlog", - srcs = ["tcp_listen_backlog_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_syncookie", - srcs = ["tcp_syncookie_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_connect_icmp_error", - srcs = ["tcp_connect_icmp_error_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "generic_dgram_socket_send_recv", - srcs = ["generic_dgram_socket_send_recv_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -validate_all_tests() - -[packetimpact_go_test( - name = t.name, - timeout = t.timeout if hasattr(t, "timeout") else "moderate", - expect_netstack_failure = hasattr(t, "expect_netstack_failure"), - num_duts = t.num_duts if hasattr(t, "num_duts") else 1, -) for t in ALL_TESTS] - -test_suite( - name = "all_tests", - tags = [ - "local", - "manual", - "packetimpact", - ], - tests = existing_rules(), -) diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go deleted file mode 100644 index cff8ca51d..000000000 --- a/test/packetimpact/tests/fin_wait2_timeout_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// 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 fin_wait2_timeout_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.Initialize(flag.CommandLine) -} - -func TestFinWait2Timeout(t *testing.T) { - for _, tt := range []struct { - description string - linger2 bool - }{ - {"WithLinger2", true}, - {"WithoutLinger2", false}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - - acceptFd, _ := dut.Accept(t, listenFd) - if tt.linger2 { - tv := unix.Timeval{Sec: 1, Usec: 0} - dut.SetSockOptTimeval(t, acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv) - } - dut.Close(t, acceptFd) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - time.Sleep(5 * time.Second) - conn.Drain(t) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if tt.linger2 { - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected a RST packet within a second but got none: %s", err) - } - } else { - if got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil { - t.Fatalf("expected no RST packets within ten seconds but got one: %s", got) - } - } - }) - } -} diff --git a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go deleted file mode 100644 index a9ffafc74..000000000 --- a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go +++ /dev/null @@ -1,781 +0,0 @@ -// Copyright 2021 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 generic_dgram_socket_send_recv_test - -import ( - "context" - "flag" - "fmt" - "net" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -const ( - // Even though sockets allow larger datagrams we don't test it here as they - // need to be fragmented and written out as individual frames. - - maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize - maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize - maxUDPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize - maxUDPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize -) - -func maxUDPPayloadSize(addr net.IP) int { - if addr.To4() != nil { - return maxUDPv4PayloadSize - } - return maxUDPv6PayloadSize -} - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer { - t.Helper() - dst := func() tcpip.LinkAddress { - if isBroadcast(dut, sendTo) { - dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1) - - // When sending to broadcast (subnet or limited), the expected ethernet - // address is also broadcast. - return header.EthernetBroadcastAddress - } - if sendTo.IsMulticast() { - if sendTo4 := sendTo.To4(); sendTo4 != nil { - return header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(sendTo4)) - } - return header.EthernetAddressFromMulticastIPv6Address(tcpip.Address(sendTo.To16())) - } - return "" - }() - var ether testbench.Ether - if len(dst) != 0 { - ether.DstAddr = &dst - } - return ðer -} - -type protocolTest interface { - Name() string - Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) - Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) -} - -func TestSocket(t *testing.T) { - dut := testbench.NewDUT(t) - subnetBroadcast := dut.Net.SubnetBroadcast() - - for _, proto := range []protocolTest{ - &icmpV4Test{}, - &icmpV6Test{}, - &udpTest{}, - } { - t.Run(proto.Name(), func(t *testing.T) { - // Test every combination of bound/unbound, broadcast/multicast/unicast - // bound/destination address, and bound/not-bound to device. - for _, bindTo := range []net.IP{ - nil, // Do not bind. - net.IPv4zero, - net.IPv4bcast, - net.IPv4allsys, - net.IPv6zero, - subnetBroadcast, - dut.Net.RemoteIPv4, - dut.Net.RemoteIPv6, - } { - t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) { - for _, sendTo := range []net.IP{ - net.IPv4bcast, - net.IPv4allsys, - subnetBroadcast, - dut.Net.LocalIPv4, - dut.Net.LocalIPv6, - dut.Net.RemoteIPv4, - dut.Net.RemoteIPv6, - } { - t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) { - for _, bindToDevice := range []bool{true, false} { - t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) { - t.Run("Send", func(t *testing.T) { - proto.Send(t, dut, bindTo, sendTo, bindToDevice) - }) - t.Run("Receive", func(t *testing.T) { - proto.Receive(t, dut, bindTo, sendTo, bindToDevice) - }) - }) - } - }) - } - }) - } - }) - } -} - -type icmpV4TestEnv struct { - socketFD int32 - ident uint16 - conn testbench.IPv4Conn - layers testbench.Layers -} - -type icmpV4Test struct{} - -func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv { - t.Helper() - - // Tell the DUT to create a socket. - var socketFD int32 - var ident uint16 - - if bindTo != nil { - socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo) - } else { - // An unbound socket will auto-bind to INADDR_ANY. - socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP) - } - t.Cleanup(func() { - dut.Close(t, socketFD) - }) - - if bindToDevice { - dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) - } - - // Create a socket on the test runner. - conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) - t.Cleanup(func() { - conn.Close(t) - }) - - return icmpV4TestEnv{ - socketFD: socketFD, - ident: ident, - conn: conn, - layers: testbench.Layers{ - expectedEthLayer(t, dut, socketFD, sendTo), - &testbench.IPv4{ - DstAddr: testbench.Address(tcpip.Address(sendTo.To4())), - }, - }, - } -} - -var _ protocolTest = (*icmpV4Test)(nil) - -func (*icmpV4Test) Name() string { return "icmpv4" } - -func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) { - // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast - // addresses. - return - } - - isV4 := sendTo.To4() != nil - - // TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow - // sending to broadcast and multicast addresses. - if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) { - // expectPacket cannot be false. In some cases the packet will send, but - // with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and - // not worth the effort to create a special case when this occurs. - t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.") - } - - expectPacket := isV4 && !sendTo.Equal(dut.Net.RemoteIPv4) - switch { - case bindTo.Equal(dut.Net.RemoteIPv4): - // If we're explicitly bound to an interface's unicast address, - // packets are always sent on that interface. - case bindToDevice: - // If we're explicitly bound to an interface, packets are always - // sent on that interface. - case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast(): - // If we're not sending to limited broadcast or multicast, the route - // table will be consulted and packets will be sent on the correct - // interface. - default: - expectPacket = false - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize), - } { - t.Run(name, func(t *testing.T) { - icmpLayer := &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4Echo), - Payload: payload, - } - bytes, err := icmpLayer.ToBytes() - if err != nil { - t.Fatalf("icmpLayer.ToBytes() = %s", err) - } - destSockaddr := unix.SockaddrInet4{} - copy(destSockaddr.Addr[:], sendTo.To4()) - - // Tell the DUT to send a packet out the ICMP socket. - if got, want := dut.SendTo(t, env.socketFD, bytes, 0, &destSockaddr), len(bytes); int(got) != want { - t.Fatalf("got dut.SendTo = %d, want %d", got, want) - } - - // Verify the test runner received an ICMP packet with the correctly - // set "ident". - if env.ident != 0 { - icmpLayer.Ident = &env.ident - } - want := append(env.layers, icmpLayer) - if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { - t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) - } else if ok && !expectPacket { - matchedFrame := got[len(got)-1] - t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) - } - }) - } -} - -func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) { - // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast - // addresses. - return - } - - expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4) - - // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor - // restricts ICMP sockets to receive only from unicast addresses. - if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) { - expectPacket = true - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize), - } { - t.Run(name, func(t *testing.T) { - icmpLayer := &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4EchoReply), - Payload: payload, - } - if env.ident != 0 { - icmpLayer.Ident = &env.ident - } - - // Send an ICMPv4 packet from the test runner to the DUT. - frame := env.conn.CreateFrame(t, env.layers, icmpLayer) - env.conn.SendFrame(t, frame) - - // Verify the behavior of the ICMP socket on the DUT. - if expectPacket { - payload, err := icmpLayer.ToBytes() - if err != nil { - t.Fatalf("icmpLayer.ToBytes() = %s", err) - } - - // Receive one extra byte to assert the length of the - // packet received in the case where the packet contains - // more data than expected. - len := int32(len(payload)) + 1 - got, want := dut.Recv(t, env.socketFD, len, 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } - } else { - // Expected receive error, set a short receive timeout. - dut.SetSockOptTimeval( - t, - env.socketFD, - unix.SOL_SOCKET, - unix.SO_RCVTIMEO, - &unix.Timeval{ - Sec: 1, - Usec: 0, - }, - ) - ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) - } - } - }) - } -} - -type icmpV6TestEnv struct { - socketFD int32 - ident uint16 - conn testbench.IPv6Conn - layers testbench.Layers -} - -// icmpV6Test and icmpV4Test look substantially similar at first look, but have -// enough subtle differences in setup and test expectations to discourage -// refactoring: -// - Different IP layers -// - Different testbench.Connections -// - Different UNIX domain and proto arguments -// - Different expectPacket and wantErrno for send and receive -type icmpV6Test struct{} - -func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv { - t.Helper() - - // Tell the DUT to create a socket. - var socketFD int32 - var ident uint16 - - if bindTo != nil { - socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo) - } else { - // An unbound socket will auto-bind to IN6ADDR_ANY_INIT. - socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6) - } - t.Cleanup(func() { - dut.Close(t, socketFD) - }) - - if bindToDevice { - dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) - } - - // Create a socket on the test runner. - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - t.Cleanup(func() { - conn.Close(t) - }) - - return icmpV6TestEnv{ - socketFD: socketFD, - ident: ident, - conn: conn, - layers: testbench.Layers{ - expectedEthLayer(t, dut, socketFD, sendTo), - &testbench.IPv6{ - DstAddr: testbench.Address(tcpip.Address(sendTo.To16())), - }, - }, - } -} - -var _ protocolTest = (*icmpV6Test)(nil) - -func (*icmpV6Test) Name() string { return "icmpv6" } - -func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - if bindTo.To4() != nil || bindTo.IsMulticast() { - // ICMPv6 sockets cannot bind to IPv4 or multicast addresses. - return - } - - expectPacket := sendTo.Equal(dut.Net.LocalIPv6) - wantErrno := unix.Errno(0) - - if sendTo.To4() != nil { - wantErrno = unix.EINVAL - } - - // TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets - // return EINVAL after calling sendto with an IPv4 address. - if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && sendTo.To4() != nil { - switch { - case bindTo.Equal(dut.Net.RemoteIPv6): - wantErrno = unix.ENETUNREACH - case bindTo.Equal(net.IPv6zero) || bindTo == nil: - wantErrno = unix.Errno(0) - } - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize), - } { - t.Run(name, func(t *testing.T) { - icmpLayer := &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Payload: payload, - } - bytes, err := icmpLayer.ToBytes() - if err != nil { - t.Fatalf("icmpLayer.ToBytes() = %s", err) - } - destSockaddr := unix.SockaddrInet6{ - ZoneId: dut.Net.RemoteDevID, - } - copy(destSockaddr.Addr[:], sendTo.To16()) - - // Tell the DUT to send a packet out the ICMPv6 socket. - gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr) - - if gotErrno != wantErrno { - t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) - } - if wantErrno != 0 { - return - } - if got, want := int(gotRet), len(bytes); got != want { - t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want) - } - - // Verify the test runner received an ICMPv6 packet with the - // correctly set "ident". - if env.ident != 0 { - icmpLayer.Ident = &env.ident - } - want := append(env.layers, icmpLayer) - if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { - t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) - } else if ok && !expectPacket { - matchedFrame := got[len(got)-1] - t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) - } - }) - } -} - -func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - if bindTo.To4() != nil || bindTo.IsMulticast() { - // ICMPv6 sockets cannot bind to IPv4 or multicast addresses. - return - } - - expectPacket := true - switch { - case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6): - case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6): - case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes): - default: - expectPacket = false - } - - // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor - // restricts ICMP sockets to receive only from unicast addresses. - if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) { - expectPacket = false - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize), - } { - t.Run(name, func(t *testing.T) { - icmpLayer := &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), - Payload: payload, - } - if env.ident != 0 { - icmpLayer.Ident = &env.ident - } - - // Send an ICMPv6 packet from the test runner to the DUT. - frame := env.conn.CreateFrame(t, env.layers, icmpLayer) - env.conn.SendFrame(t, frame) - - // Verify the behavior of the ICMPv6 socket on the DUT. - if expectPacket { - payload, err := icmpLayer.ToBytes() - if err != nil { - t.Fatalf("icmpLayer.ToBytes() = %s", err) - } - - // Receive one extra byte to assert the length of the - // packet received in the case where the packet contains - // more data than expected. - len := int32(len(payload)) + 1 - got, want := dut.Recv(t, env.socketFD, len, 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } - } else { - // Expected receive error, set a short receive timeout. - dut.SetSockOptTimeval( - t, - env.socketFD, - unix.SOL_SOCKET, - unix.SO_RCVTIMEO, - &unix.Timeval{ - Sec: 1, - Usec: 0, - }, - ) - ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) - } - } - }) - } -} - -type udpConn interface { - SrcPort(*testing.T) uint16 - SendFrame(*testing.T, testbench.Layers, ...testbench.Layer) - ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool) - Close(*testing.T) -} - -type udpTestEnv struct { - socketFD int32 - conn udpConn - layers testbench.Layers -} - -type udpTest struct{} - -func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv { - t.Helper() - - var ( - socketFD int32 - outgoingUDP, incomingUDP testbench.UDP - ) - - // Tell the DUT to create a socket. - if bindTo != nil { - var remotePort uint16 - socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo) - outgoingUDP.DstPort = &remotePort - incomingUDP.SrcPort = &remotePort - } else { - // An unbound socket will auto-bind to INADDR_ANY. - socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) - } - t.Cleanup(func() { - dut.Close(t, socketFD) - }) - - if bindToDevice { - dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) - } - - // Create a socket on the test runner. - var conn udpConn - var ipLayer testbench.Layer - if addr := sendTo.To4(); addr != nil { - udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP) - conn = &udpConn - ipLayer = &testbench.IPv4{ - DstAddr: testbench.Address(tcpip.Address(addr)), - } - } else { - udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP) - conn = &udpConn - ipLayer = &testbench.IPv6{ - DstAddr: testbench.Address(tcpip.Address(sendTo.To16())), - } - } - t.Cleanup(func() { - conn.Close(t) - }) - - return udpTestEnv{ - socketFD: socketFD, - conn: conn, - layers: testbench.Layers{ - expectedEthLayer(t, dut, socketFD, sendTo), - ipLayer, - &incomingUDP, - }, - } -} - -var _ protocolTest = (*udpTest)(nil) - -func (*udpTest) Name() string { return "udp" } - -func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - canSend := bindTo == nil || bindTo.Equal(net.IPv6zero) || sameIPVersion(sendTo, bindTo) - expectPacket := canSend && !isRemoteAddr(dut, sendTo) - switch { - case bindTo.Equal(dut.Net.RemoteIPv4): - // If we're explicitly bound to an interface's unicast address, - // packets are always sent on that interface. - case bindToDevice: - // If we're explicitly bound to an interface, packets are always - // sent on that interface. - case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast(): - // If we're not sending to limited broadcast, multicast, or local, the - // route table will be consulted and packets will be sent on the correct - // interface. - default: - expectPacket = false - } - - wantErrno := unix.Errno(0) - switch { - case !canSend && bindTo.To4() != nil: - wantErrno = unix.EAFNOSUPPORT - case !canSend && bindTo.To4() == nil: - wantErrno = unix.ENETUNREACH - } - - // TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets - // returns EAFNOSUPPORT after calling sendto with an IPv6 address. - if dut.Uname.IsGvisor() && !canSend && bindTo.To4() != nil { - wantErrno = unix.EINVAL - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)), - } { - t.Run(name, func(t *testing.T) { - var destSockaddr unix.Sockaddr - if sendTo4 := sendTo.To4(); sendTo4 != nil { - addr := unix.SockaddrInet4{ - Port: int(env.conn.SrcPort(t)), - } - copy(addr.Addr[:], sendTo4) - destSockaddr = &addr - } else { - addr := unix.SockaddrInet6{ - Port: int(env.conn.SrcPort(t)), - ZoneId: dut.Net.RemoteDevID, - } - copy(addr.Addr[:], sendTo.To16()) - destSockaddr = &addr - } - - // Tell the DUT to send a packet out the UDP socket. - gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr) - - if gotErrno != wantErrno { - t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) - } - if wantErrno != 0 { - return - } - if got, want := int(gotRet), len(payload); got != want { - t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want) - } - - // Verify the test runner received a UDP packet with the - // correct payload. - want := append(env.layers, &testbench.Payload{ - Bytes: payload, - }) - if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { - t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) - } else if ok && !expectPacket { - matchedFrame := got[len(got)-1] - t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) - } - }) - } -} - -func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { - subnetBroadcast := dut.Net.SubnetBroadcast() - - expectPacket := true - switch { - case bindTo.Equal(sendTo): - case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4): - case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo): - case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo): - case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast): - default: - expectPacket = false - } - - // TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor - // restricts ICMP sockets to receive only from unicast addresses. - if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) { - expectPacket = true - } - - env := test.setup(t, dut, bindTo, sendTo, bindToDevice) - maxPayloadSize := maxUDPPayloadSize(bindTo) - - for name, payload := range map[string][]byte{ - "empty": nil, - "small": []byte("hello world"), - "random1k": testbench.GenerateRandomPayload(t, maxPayloadSize), - } { - t.Run(name, func(t *testing.T) { - // Send a UDP packet from the test runner to the DUT. - env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload}) - - // Verify the behavior of the ICMP socket on the DUT. - if expectPacket { - // Receive one extra byte to assert the length of the - // packet received in the case where the packet contains - // more data than expected. - len := int32(len(payload)) + 1 - got, want := dut.Recv(t, env.socketFD, len, 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } - } else { - // Expected receive error, set a short receive timeout. - dut.SetSockOptTimeval( - t, - env.socketFD, - unix.SOL_SOCKET, - unix.SO_RCVTIMEO, - &unix.Timeval{ - Sec: 1, - Usec: 0, - }, - ) - ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) - } - } - }) - } -} - -func isBroadcast(dut testbench.DUT, ip net.IP) bool { - return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast()) -} - -func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool { - return isBroadcast(dut, ip) || ip.IsMulticast() -} - -func sameIPVersion(a, b net.IP) bool { - return (a.To4() == nil) == (b.To4() == nil) -} - -func isRemoteAddr(dut testbench.DUT, ip net.IP) bool { - return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6) -} diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go deleted file mode 100644 index fdabcf1ad..000000000 --- a/test/packetimpact/tests/icmpv6_param_problem_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// 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 icmpv6_param_problem_test - -import ( - "flag" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestICMPv6ParamProblemTest sends a packet with a bad next header. The DUT -// should respond with an ICMPv6 Parameter Problem message. -func TestICMPv6ParamProblemTest(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - ipv6 := testbench.IPv6{ - // 254 is reserved and used for experimentation and testing. This should - // cause an error. - NextHeader: testbench.Uint8(254), - } - icmpv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Payload: []byte("hello world"), - } - - toSend := conn.CreateFrame(t, testbench.Layers{&ipv6}, &icmpv6) - conn.SendFrame(t, toSend) - - // Build the expected ICMPv6 payload, which includes an index to the - // problematic byte and also the problematic packet as described in - // https://tools.ietf.org/html/rfc4443#page-12 . - ipv6Sent := toSend[1:] - expectedPayload, err := ipv6Sent.ToBytes() - if err != nil { - t.Fatalf("can't convert %s to bytes: %s", ipv6Sent, err) - } - - expectedICMPv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem), - Payload: expectedPayload, - // The problematic field is the NextHeader. - Pointer: testbench.Uint32(header.IPv6NextHeaderOffset), - } - - paramProblem := testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &expectedICMPv6, - } - timeout := time.Second - if _, err := conn.ExpectFrame(t, paramProblem, timeout); err != nil { - t.Errorf("expected %s within %s but got none: %s", paramProblem, timeout, err) - } -} diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go deleted file mode 100644 index 707f0f1f5..000000000 --- a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// 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 ipv4_fragment_reassembly_test - -import ( - "flag" - "math/rand" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type fragmentInfo struct { - offset uint16 - size uint16 - more uint8 - id uint16 -} - -func TestIPv4FragmentReassembly(t *testing.T) { - icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber) - - tests := []struct { - description string - ipPayloadLen int - fragments []fragmentInfo - expectReply bool - }{ - { - description: "basic reassembly", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 5, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 5, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 5, more: 0}, - }, - expectReply: true, - }, - { - description: "out of order fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 2000, size: 1000, id: 6, more: 0}, - {offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, - }, - expectReply: true, - }, - { - description: "duplicated fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 7, more: 0}, - }, - expectReply: true, - }, - { - description: "fragment subset", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 8, more: 0}, - }, - expectReply: true, - }, - { - description: "fragment overlap", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 9, more: 0}, - }, - expectReply: false, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) - defer conn.Close(t) - - data := make([]byte, test.ipPayloadLen) - icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize]) - icmp.SetType(header.ICMPv4Echo) - icmp.SetCode(header.ICMPv4UnusedCode) - icmp.SetChecksum(0) - icmp.SetSequence(0) - icmp.SetIdent(0) - originalPayload := data[header.ICMPv4MinimumSize:] - if _, err := rand.Read(originalPayload); err != nil { - t.Fatalf("rand.Read: %s", err) - } - cksum := header.ICMPv4Checksum(icmp, header.Checksum(originalPayload, 0 /* initial */)) - icmp.SetChecksum(cksum) - - for _, fragment := range test.fragments { - conn.Send(t, - testbench.IPv4{ - Protocol: &icmpv4ProtoNum, - FragmentOffset: testbench.Uint16(fragment.offset), - Flags: testbench.Uint8(fragment.more), - ID: testbench.Uint16(fragment.id), - }, - &testbench.Payload{ - Bytes: data[fragment.offset:][:fragment.size], - }) - } - - var bytesReceived int - reassembledPayload := make([]byte, test.ipPayloadLen) - // We are sending a packet fragmented into smaller parts but the - // response may also be large enough to require fragmentation. - // Therefore we only look for payload for an IPv4 packet not ICMP. - for { - incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv4{}, - }, time.Second) - if err != nil { - // Either an unexpected frame was received, or none at all. - if test.expectReply && bytesReceived < test.ipPayloadLen { - t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) - } - break - } - if !test.expectReply { - t.Fatalf("unexpected reply received:\n%s", incomingFrame) - } - // We only asked for Ethernet and IPv4 so the rest should be payload. - ipPayload, err := incomingFrame[2 /* Payload */].ToBytes() - if err != nil { - t.Fatalf("failed to parse payload: incomingPacket[2].ToBytes() = (_, %s)", err) - } - offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset - if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { - t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) - } - bytesReceived += len(ipPayload) - } - - if test.expectReply { - if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" { - t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go deleted file mode 100644 index 2b69ceecb..000000000 --- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// 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 ipv4_id_uniqueness_test - -import ( - "context" - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) { - layers, err := conn.ExpectData(t, expect, expectPayload, time.Second) - if err != nil { - return 0, fmt.Errorf("failed to receive TCP segment: %s", err) - } - if len(layers) < 2 { - return 0, fmt.Errorf("got packet with layers: %v, expected to have at least 2 layers (link and network)", layers) - } - ipv4, ok := layers[1].(*testbench.IPv4) - if !ok { - return 0, fmt.Errorf("got network layer: %T, expected: *IPv4", layers[1]) - } - if *ipv4.Flags&header.IPv4FlagDontFragment != 0 { - return 0, fmt.Errorf("got IPv4 DF=1, expected DF=0") - } - return *ipv4.ID, nil -} - -// RFC 6864 section 4.2 states: "The IPv4 ID of non-atomic datagrams MUST NOT -// be reused when sending a copy of an earlier non-atomic datagram." -// -// This test creates a TCP connection, uses the IP_MTU_DISCOVER socket option -// to force the DF bit to be 0, and checks that a retransmitted segment has a -// different IPv4 Identification value than the original segment. -func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) { - for _, tc := range []struct { - name string - payload []byte - }{ - {"SmallPayload", []byte("sample data")}, - // 512 bytes is chosen because sending more than this in a single segment - // causes the retransmission to send less than the original amount. - {"512BytePayload", testbench.GenerateRandomPayload(t, 512)}, - } { - t.Run(tc.name, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - remoteFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, remoteFD) - - dut.SetSockOptInt(t, remoteFD, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - // TODO(b/129291778) The following socket option clears the DF bit on - // IP packets sent over the socket, and is currently not supported by - // gVisor. gVisor by default sends packets with DF=0 anyway, so the - // socket option being not supported does not affect the operation of - // this test. Once the socket option is supported, the following call - // can be changed to simply assert success. - ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT) - // Fuchsia will return ENOPROTOPT errno. - if ret == -1 && errno != unix.ENOPROTOOPT { - t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno) - } - - samplePayload := &testbench.Payload{Bytes: tc.payload} - - dut.Send(t, remoteFD, tc.payload, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("failed to receive TCP segment sent for RTT calculation: %s", err) - } - // Let the DUT estimate RTO with RTT from the DATA-ACK. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip sending this ACK. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Send(t, remoteFD, tc.payload, 0) - expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))} - originalID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload) - if err != nil { - t.Fatalf("failed to receive TCP segment: %s", err) - } - - retransmitID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload) - if err != nil { - t.Fatalf("failed to receive retransmitted TCP segment: %s", err) - } - if originalID == retransmitID { - t.Fatalf("unexpectedly got retransmitted TCP segment with same IPv4 ID field=%d", originalID) - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go deleted file mode 100644 index 4034a128e..000000000 --- a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go +++ /dev/null @@ -1,356 +0,0 @@ -// 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 ipv6_fragment_icmp_error_test - -import ( - "flag" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -const ( - data = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT" - fragmentID = 1 - reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.IPv6Conn, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) { - t.Helper() - - icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize)) - icmpv6Header.SetType(header.ICMPv6EchoRequest) - icmpv6Header.SetCode(header.ICMPv6UnusedCode) - icmpv6Header.SetIdent(0) - icmpv6Header.SetSequence(0) - cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmpv6Header, - Src: tcpip.Address(n.LocalIPv6), - Dst: tcpip.Address(n.RemoteIPv6), - PayloadCsum: header.Checksum(payload, 0 /* initial */), - PayloadLen: len(payload), - }) - icmpv6Header.SetChecksum(cksum) - icmpv6Bytes := append([]byte(icmpv6Header), payload...) - - icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - - firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.Payload{ - Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength], - }, - ) - firstIPv6 := firstFragment[1:] - firstIPv6Bytes, err := firstIPv6.ToBytes() - if err != nil { - t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err) - } - - secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(secondFragmentOffset), - MoreFragments: testbench.Bool(false), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.Payload{ - Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:], - }, - ) - secondIPv6 := secondFragment[1:] - secondIPv6Bytes, err := secondIPv6.ToBytes() - if err != nil { - t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err) - } - - return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes} -} - -func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) { - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - }{ - { - name: "reassemble two fragments", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1, 2}, - }, - { - name: "reassemble two fragments in reverse order", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{2, 1}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotEchoReply, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - }, - }, time.Second) - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err) - } - gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:] - wantPayload := test.payload - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestIPv6FragmentReassemblyTimeout(t *testing.T) { - type icmpFramePattern struct { - typ header.ICMPv6Type - code header.ICMPv6Code - } - - type icmpReassemblyTimeoutDetail struct { - payloadFragment int // 1: first fragment, 2: second fragnemt. - } - - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - replyFilter icmpFramePattern - expectErrorReply bool - expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail - }{ - { - name: "reassembly timeout (first fragment only)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: true, - expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ - payloadFragment: 1, - }, - }, - { - name: "reassembly timeout (second fragment only)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: false, - }, - { - name: "reassembly timeout (two fragments with a gap)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8, - sendFrameOrder: []int{1, 2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: true, - expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ - payloadFragment: 1, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(test.replyFilter.typ), - Code: testbench.ICMPv6Code(test.replyFilter.code), - }, - }, reassemblyTimeout) - if !test.expectErrorReply { - if err == nil { - t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code) - } - return - } - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) - } - gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] - wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1] - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestIPv6FragmentParamProblem(t *testing.T) { - type icmpFramePattern struct { - typ header.ICMPv6Type - code header.ICMPv6Code - } - - type icmpParamProblemDetail struct { - pointer uint32 - payloadFragment int // 1: first fragment, 2: second fragnemt. - } - - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - replyFilter icmpFramePattern - expectICMPParamProblem icmpParamProblemDetail - }{ - { - name: "payload size not a multiple of 8", - firstPayloadLength: 9, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6ParamProblem, - code: header.ICMPv6ErroneousHeader, - }, - expectICMPParamProblem: icmpParamProblemDetail{ - pointer: 4, - payloadFragment: 1, - }, - }, - { - name: "payload length error", - firstPayloadLength: 16, - payload: []byte(data)[:33], - secondFragmentOffset: 65520 / 8, - sendFrameOrder: []int{1, 2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6ParamProblem, - code: header.ICMPv6ErroneousHeader, - }, - expectICMPParamProblem: icmpParamProblemDetail{ - pointer: 42, - payloadFragment: 2, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(test.replyFilter.typ), - Code: testbench.ICMPv6Code(test.replyFilter.code), - }, - }, time.Second) - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) - } - gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - gotPointer := header.ICMPv6(gotPayload).TypeSpecific() - wantPointer := test.expectICMPParamProblem.pointer - if gotPointer != wantPointer { - t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer) - } - icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] - wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1] - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go deleted file mode 100644 index db6195dc9..000000000 --- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// 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 ipv6_fragment_reassembly_test - -import ( - "flag" - "math/rand" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type fragmentInfo struct { - offset uint16 - size uint16 - more bool - id uint32 -} - -func TestIPv6FragmentReassembly(t *testing.T) { - icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - - tests := []struct { - description string - ipPayloadLen int - fragments []fragmentInfo - expectReply bool - }{ - { - description: "basic reassembly", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 100, more: true}, - {offset: 1000, size: 1000, id: 100, more: true}, - {offset: 2000, size: 1000, id: 100, more: false}, - }, - expectReply: true, - }, - { - description: "out of order fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 101, more: true}, - {offset: 2000, size: 1000, id: 101, more: false}, - {offset: 1000, size: 1000, id: 101, more: true}, - }, - expectReply: true, - }, - { - description: "duplicated fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 102, more: true}, - {offset: 1000, size: 1000, id: 102, more: true}, - {offset: 1000, size: 1000, id: 102, more: true}, - {offset: 2000, size: 1000, id: 102, more: false}, - }, - expectReply: true, - }, - { - description: "fragment subset", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 103, more: true}, - {offset: 1000, size: 1000, id: 103, more: true}, - {offset: 512, size: 256, id: 103, more: true}, - {offset: 2000, size: 1000, id: 103, more: false}, - }, - expectReply: true, - }, - { - description: "fragment overlap", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 104, more: true}, - {offset: 1512, size: 1000, id: 104, more: true}, - {offset: 1000, size: 1000, id: 104, more: true}, - {offset: 2000, size: 1000, id: 104, more: false}, - }, - expectReply: false, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - lIP := tcpip.Address(dut.Net.LocalIPv6) - rIP := tcpip.Address(dut.Net.RemoteIPv6) - - data := make([]byte, test.ipPayloadLen) - icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize]) - icmp.SetType(header.ICMPv6EchoRequest) - icmp.SetCode(header.ICMPv6UnusedCode) - icmp.SetChecksum(0) - originalPayload := data[header.ICMPv6HeaderSize:] - if _, err := rand.Read(originalPayload); err != nil { - t.Fatalf("rand.Read: %s", err) - } - - cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmp, - Src: lIP, - Dst: rIP, - PayloadCsum: header.Checksum(originalPayload, 0 /* initial */), - PayloadLen: len(originalPayload), - }) - icmp.SetChecksum(cksum) - - for _, fragment := range test.fragments { - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit), - MoreFragments: testbench.Bool(fragment.more), - Identification: testbench.Uint32(fragment.id), - }, - &testbench.Payload{ - Bytes: data[fragment.offset:][:fragment.size], - }) - } - - var bytesReceived int - reassembledPayload := make([]byte, test.ipPayloadLen) - for { - incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{}, - }, time.Second) - if err != nil { - // Either an unexpected frame was received, or none at all. - if test.expectReply && bytesReceived < test.ipPayloadLen { - t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) - } - break - } - if !test.expectReply { - t.Fatalf("unexpected reply received:\n%s", incomingFrame) - } - ipPayload, err := incomingFrame[3 /* Payload */].ToBytes() - if err != nil { - t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err) - } - offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset - offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit - if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { - t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) - } - bytesReceived += len(ipPayload) - } - - if test.expectReply { - if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" { - t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go deleted file mode 100644 index d762c43a7..000000000 --- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go +++ /dev/null @@ -1,183 +0,0 @@ -// 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 ipv6_unknown_options_action_test - -import ( - "flag" - "net" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer { - return &testbench.IPv6HopByHopOptionsExtHdr{ - Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00}, - } -} - -func mkDestinationOptionsExtHdr(optType byte) testbench.Layer { - return &testbench.IPv6DestinationOptionsExtHdr{ - Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00}, - } -} - -func optionTypeFromAction(action header.IPv6OptionUnknownAction) byte { - return byte(action << 6) -} - -func TestIPv6UnknownOptionAction(t *testing.T) { - for _, tt := range []struct { - description string - mkExtHdr func(optType byte) testbench.Layer - action header.IPv6OptionUnknownAction - multicastDst bool - wantICMPv6 bool - }{ - { - description: "0b00/hbh", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionSkip, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b01/hbh", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscard, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b10/hbh/unicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b10/hbh/multicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: true, - wantICMPv6: true, - }, - { - description: "0b11/hbh/unicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b11/hbh/multicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: true, - wantICMPv6: false, - }, - { - description: "0b00/destination", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionSkip, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b01/destination", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscard, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b10/destination/unicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b10/destination/multicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: true, - wantICMPv6: true, - }, - { - description: "0b11/destination/unicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b11/destination/multicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: true, - wantICMPv6: false, - }, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - outgoingOverride := testbench.Layers{} - if tt.multicastDst { - outgoingOverride = testbench.Layers{&testbench.IPv6{ - DstAddr: testbench.Address(tcpip.Address(net.ParseIP("ff02::1"))), - }} - } - - outgoing := conn.CreateFrame(t, outgoingOverride, tt.mkExtHdr(optionTypeFromAction(tt.action))) - conn.SendFrame(t, outgoing) - ipv6Sent := outgoing[1:] - icmpv6Payload, err := ipv6Sent.ToBytes() - if err != nil { - t.Fatalf("failed to serialize the outgoing packet: %s", err) - } - gotICMPv6, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem), - Code: testbench.ICMPv6Code(header.ICMPv6UnknownOption), - // The pointer in the ICMPv6 parameter problem message - // should point to the option type of the unknown option. In - // our test case, it is the first option in the extension - // header whose option type is 2 bytes after the IPv6 header - // (after NextHeader and ExtHdrLen). - Pointer: testbench.Uint32(header.IPv6MinimumSize + 2), - Payload: icmpv6Payload, - }, - }, time.Second) - if tt.wantICMPv6 && err != nil { - t.Fatalf("expected ICMPv6 Parameter Problem but got none: %s", err) - } - if !tt.wantICMPv6 && gotICMPv6 != nil { - t.Fatalf("expected no ICMPv6 Parameter Problem but got one: %s", gotICMPv6) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_connect_icmp_error_test.go b/test/packetimpact/tests/tcp_connect_icmp_error_test.go deleted file mode 100644 index 15d603328..000000000 --- a/test/packetimpact/tests/tcp_connect_icmp_error_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2021 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_connect_icmp_error_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.Initialize(flag.CommandLine) -} - -func sendICMPError(t *testing.T, conn *testbench.TCPIPv4, tcp *testbench.TCP) { - t.Helper() - - icmpPayload := testbench.Layers{tcp.Prev(), tcp} - bytes, err := icmpPayload.ToBytes() - if err != nil { - t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) - } - - layers := conn.CreateFrame(t, nil) - layers[len(layers)-1] = &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable), - Payload: bytes, - } - conn.SendFrameStateless(t, layers) -} - -// TestTCPConnectICMPError tests for the handshake to fail and the socket state -// cleaned up on receiving an ICMP error. -func TestTCPConnectICMPError(t *testing.T) { - dut := testbench.NewDUT(t) - - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - port := uint16(9001) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) - defer conn.Close(t) - sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], dut.Net.LocalIPv4) - // Bring the dut to SYN-SENT state with a non-blocking connect. - dut.Connect(t, clientFD, &sa) - tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) - if err != nil { - t.Fatalf("expected SYN, %s", err) - } - - // Continuously try to read the ICMP error in an attempt to trigger a race - // condition. - start := make(chan struct{}) - done := make(chan struct{}) - go func() { - defer close(done) - - close(start) - for { - select { - case <-done: - return - default: - } - const want = unix.EHOSTUNREACH - switch got := unix.Errno(dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR)); got { - case unix.Errno(0): - continue - case want: - return - default: - t.Fatalf("got SO_ERROR = %s, want %s", got, want) - } - - } - }() - - <-start - sendICMPError(t, &conn, tcp) - - dut.PollOne(t, clientFD, unix.POLLHUP, time.Second) - <-done - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // The DUT should reply with RST to our ACK as the state should have - // transitioned to CLOSED because of handshake error. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected RST, %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go deleted file mode 100644 index 1db3c9883..000000000 --- a/test/packetimpact/tests/tcp_cork_mss_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// 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_cork_mss_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.Initialize(flag.CommandLine) -} - -// TestTCPCorkMSS tests for segment coalesce and split as per MSS. -func TestTCPCorkMSS(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - const mss = uint32(header.TCPDefaultMSS) - options := make([]byte, header.TCPOptionMSSLength) - header.EncodeMSSOption(mss, options) - conn.ConnectWithOptions(t, options) - - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - dut.SetSockOptInt(t, acceptFD, unix.IPPROTO_TCP, unix.TCP_CORK, 1) - - // Let the dut application send 2 small segments to be held up and coalesced - // until the application sends a larger segment to fill up to > MSS. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - dut.Send(t, acceptFD, sampleData, 0) - - expectedData := sampleData - expectedData = append(expectedData, sampleData...) - largeData := make([]byte, mss+1) - expectedData = append(expectedData, largeData...) - dut.Send(t, acceptFD, largeData, 0) - - // Expect the segments to be coalesced and sent and capped to MSS. - expectedPayload := testbench.Payload{Bytes: expectedData[:mss]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the coalesced segment to be split and transmitted. - expectedPayload = testbench.Payload{Bytes: expectedData[mss:]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check for segments to *not* be held up because of TCP_CORK when - // the current send window is less than MSS. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))}) - dut.Send(t, acceptFD, sampleData, 0) - dut.Send(t, acceptFD, sampleData, 0) - expectedPayload = testbench.Payload{Bytes: append(sampleData, sampleData...)} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} diff --git a/test/packetimpact/tests/tcp_fin_retransmission_test.go b/test/packetimpact/tests/tcp_fin_retransmission_test.go deleted file mode 100644 index 500f7a783..000000000 --- a/test/packetimpact/tests/tcp_fin_retransmission_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// 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_fin_retransmission_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.Initialize(flag.CommandLine) -} - -// TestTCPClosingFinRetransmission tests that TCP implementation should retransmit -// FIN segment in CLOSING state. -func TestTCPClosingFinRetransmission(t *testing.T) { - for _, tt := range []struct { - description string - flags header.TCPFlags - }{ - {"CLOSING", header.TCPFlagAck | header.TCPFlagFin}, - {"FIN_WAIT_1", header.TCPFlagAck}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip the next block of code. - sampleData := []byte("Sample Data") - if got, want := dut.Send(t, acceptFD, sampleData, 0), len(sampleData); int(got) != want { - t.Fatalf("got dut.Send(t, %d, %s, 0) = %d, want %d", acceptFD, sampleData, got, want) - } - if _, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected FINACK from DUT, but got none: %s", err) - } - - // Do not ack the FIN from DUT so that we can test for retransmission. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(tt.flags)}) - - if tt.flags&header.TCPFlagFin != 0 { - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - } - - if _, err := conn.Expect(t, testbench.TCP{ - SeqNum: seqNumForTheirFIN, - Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck), - }, time.Second); err != nil { - t.Errorf("expected retransmission of FIN from the DUT: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go deleted file mode 100644 index 668e0275c..000000000 --- a/test/packetimpact/tests/tcp_handshake_window_size_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Initialize(flag.CommandLine) -} - -// TestTCPHandshakeWindowSize tests if the stack is honoring the window size -// communicated during handshake. -func TestTCPHandshakeWindowSize(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - // Start handshake with zero window size. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(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(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))}) - - acceptFd, _ := dut.Accept(t, listenFD) - defer dut.Close(t, 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(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go deleted file mode 100644 index 5410cc368..000000000 --- a/test/packetimpact/tests/tcp_info_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// 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_info_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestTCPInfo(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - // Send and receive sample data. - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - dut.Send(t, acceptFD, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - info := dut.GetSockOptTCPInfo(t, acceptFD) - if got, want := uint32(info.State), linux.TCP_ESTABLISHED; got != want { - t.Fatalf("got %d want %d", got, want) - } - if info.RTT == 0 { - t.Errorf("got RTT=0, want nonzero") - } - if info.RTTVar == 0 { - t.Errorf("got RTTVar=0, want nonzero") - } - if info.RTO == 0 { - t.Errorf("got RTO=0, want nonzero") - } - if info.ReordSeen != 0 { - t.Errorf("expected the connection to not have any reordering, got: %d want: 0", info.ReordSeen) - } - if info.SndCwnd == 0 { - t.Errorf("expected send congestion window to be greater than zero") - } - if info.CaState != linux.TCP_CA_Open { - t.Errorf("expected the connection to be in open state, got: %d want: %d", info.CaState, linux.TCP_CA_Open) - } - - if t.Failed() { - t.FailNow() - } - - // Check the congestion control state and send congestion window after - // retransmission timeout. - seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - dut.Send(t, acceptFD, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) - } - - // Given a generous retransmission timeout. - timeout := time.Duration(info.RTO) * 2 * time.Microsecond - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil { - t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) - } - - info = dut.GetSockOptTCPInfo(t, acceptFD) - if info.CaState != linux.TCP_CA_Loss { - t.Errorf("expected the connection to be in loss recovery, got: %d want: %d", info.CaState, linux.TCP_CA_Loss) - } - if info.SndCwnd != 1 { - t.Errorf("expected send congestion window to be 1, got: %d", info.SndCwnd) - } -} diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go deleted file mode 100644 index 46b5ca5d8..000000000 --- a/test/packetimpact/tests/tcp_linger_test.go +++ /dev/null @@ -1,265 +0,0 @@ -// 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_linger_test - -import ( - "context" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) { - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - return acceptFD, listenFD, conn -} - -func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) { - conn.Close(t) - dut.Close(t, listenFD) -} - -// lingerDuration is the timeout value used with SO_LINGER socket option. -const lingerDuration = 3 * time.Second - -// TestTCPLingerZeroTimeout tests when SO_LINGER is set with zero timeout. DUT -// should send RST-ACK when socket is closed. -func TestTCPLingerZeroTimeout(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, 0, true) - dut.Close(t, acceptFD) - - // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected RST-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK -// when socket is closed. -func TestTCPLingerOff(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.Close(t, acceptFD) - - // If SO_LINGER is not set, DUT should send a FIN-ACK. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout. -// DUT should close the socket after timeout. -func TestTCPLingerNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"WithNonZeroLinger", true}, - {"WithoutLinger", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - start := time.Now() - dut.CloseWithErrno(context.Background(), t, acceptFD) - elapsed := time.Since(start) - - expectedMaximum := time.Second - if tt.lingerOn { - expectedMaximum += lingerDuration - if elapsed < lingerDuration { - t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) - } - } - if elapsed >= expectedMaximum { - t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -// TestTCPLingerSendNonZeroTimeout tests when SO_LINGER is set with non-zero -// timeout and send a packet. DUT should close the socket after timeout. -func TestTCPLingerSendNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"WithSendNonZeroLinger", true}, - {"WithoutLinger", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - // Send data. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - - start := time.Now() - dut.CloseWithErrno(context.Background(), t, acceptFD) - elapsed := time.Since(start) - - expectedMaximum := time.Second - if tt.lingerOn { - expectedMaximum += lingerDuration - if elapsed < lingerDuration { - t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) - } - } - if elapsed >= expectedMaximum { - t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) - } - - samplePayload := &testbench.Payload{Bytes: sampleData} - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -// TestTCPLingerShutdownZeroTimeout tests SO_LINGER with shutdown() and zero -// timeout. DUT should send RST-ACK when socket is closed. -func TestTCPLingerShutdownZeroTimeout(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, 0, true) - dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) - dut.Close(t, acceptFD) - - // Shutdown will send FIN-ACK with read/write option. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - - // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected RST-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and -// non-zero timeout. DUT should close the socket after timeout. -func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"shutdownRDWR", true}, - {"shutdownRDWR", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - // Send data. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - - dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) - - start := time.Now() - dut.CloseWithErrno(context.Background(), t, acceptFD) - elapsed := time.Since(start) - - expectedMaximum := time.Second - if tt.lingerOn { - expectedMaximum += lingerDuration - if elapsed < lingerDuration { - t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) - } - } - if elapsed >= expectedMaximum { - t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) - } - - samplePayload := &testbench.Payload{Bytes: sampleData} - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -func TestTCPLingerNonEstablished(t *testing.T) { - dut := testbench.NewDUT(t) - newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) - dut.SetSockLingerOption(t, newFD, lingerDuration, true) - - // As the socket is in the initial state, Close() should not linger - // and return immediately. - start := time.Now() - dut.CloseWithErrno(context.Background(), t, newFD) - elapsed := time.Since(start) - - expectedMaximum := time.Second - if elapsed >= time.Second { - t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) - } -} diff --git a/test/packetimpact/tests/tcp_listen_backlog_test.go b/test/packetimpact/tests/tcp_listen_backlog_test.go deleted file mode 100644 index e124002f6..000000000 --- a/test/packetimpact/tests/tcp_listen_backlog_test.go +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2021 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_listen_backlog_test - -import ( - "bytes" - "flag" - "sync" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPListenBacklog tests for a listening endpoint behavior: -// (1) reply to more SYNs than what is configured as listen backlog -// (2) ignore ACKs (that complete a handshake) when the accept queue is full -// (3) ignore incoming SYNs when the accept queue is full -func TestTCPListenBacklog(t *testing.T) { - dut := testbench.NewDUT(t) - - // This is the number of pending connections before SYN cookies are used. - const backlog = 10 - - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, backlog) - defer dut.Close(t, listenFd) - - // Fill the SYN queue with connections in SYN-RCVD. We will use these to test - // that ACKs received while the accept queue is full are ignored. - var synQueueConns [backlog]testbench.TCPIPv4 - defer func() { - for i := range synQueueConns { - synQueueConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - const payloadLen = 1 - payload := testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - - // Fill the accept queue with connections established using SYN cookies. - var synCookieConns [backlog + 1]testbench.TCPIPv4 - defer func() { - for i := range synCookieConns { - synCookieConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range synCookieConns { - conn := &synCookieConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - // Send a payload so we can observe the dut ACK. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &payload) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // Send ACKs to complete the handshakes. These are expected to be dropped - // because the accept queue is full. - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Wait for the SYN-ACK to be retransmitted to confirm the ACK was - // dropped. - seqNum := uint32(*conn.RemoteSeqNum(t) - 1) - if got, err := conn.Expect(t, testbench.TCP{SeqNum: &seqNum}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // While the accept queue is still full, send an unexpected ACK from a new - // socket. The listener should reply with an RST. - func() { - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - defer conn.Close(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("expected TCP frame: %s", err) - } else if got, want := *got.Flags, header.TCPFlagRst; got != want { - t.Errorf("got %s, want %s", got, want) - } - }() - - func() { - // Now initiate a new connection when the accept queue is full. - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - defer conn.Close(t) - // Expect dut connection to drop the SYN. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Fatalf("expected no TCP frame, got %s", got) - } - }() - - // Drain the accept queue. - { - var wg sync.WaitGroup - for i := range synCookieConns { - conn := &synCookieConns[i] - - wg.Add(1) - go func(i int) { - defer wg.Done() - - fd, _ := dut.Accept(t, listenFd) - b := dut.Recv(t, fd, payloadLen+1, 0) - dut.Close(t, fd) - if !bytes.Equal(b, payload.Bytes) { - t.Errorf("connection %d: got dut.Recv = %x, want = %x", i, b, payload.Bytes) - } - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagFin|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - - // Prevent retransmission. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // Complete the partial connections to move them from the SYN queue to the - // accept queue. We will use these to test that connections in the accept - // queue are closed on listener shutdown. - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - tcp := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)} - - // Exercise connections with and without pending data. - if i%2 == 0 { - // Send ACK with no payload; wait for absence of SYN-ACK retransmit. - conn.Send(t, tcp) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Errorf("%d: expected no TCP frame, got %s", i, got) - } - } else { - // Send ACK with payload; wait for ACK. - conn.Send(t, tcp, &payload) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - } - }(i) - } - - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // The accept queue now has N-1 connections in it. The next incoming SYN will - // enter the SYN queue, and the one following will use SYN cookies. We test - // both. - var connectingConns [2]testbench.TCPIPv4 - defer func() { - for i := range connectingConns { - connectingConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range connectingConns { - conn := &connectingConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - dut.Shutdown(t, listenFd, unix.SHUT_RD) - - var wg sync.WaitGroup - - // Shutdown causes Connections in the accept queue to be closed. - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagRst|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - - for i := range connectingConns { - conn := &connectingConns[i] - - wg.Add(1) - go func(i int) { - defer wg.Done() - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Errorf("%d: expected no TCP frame, got %s", i, got) - } - }(i) - } - - wg.Wait() -} diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go deleted file mode 100644 index e92e6aa9b..000000000 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// 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_synsent_reset_test - -import ( - "context" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when -// an ICMP destination unreachable message is sent in response to the inital -// SYN. -func TestTCPSynSentUnreachable(t *testing.T) { - // Create the DUT and connection. - dut := testbench.NewDUT(t) - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - port := uint16(9001) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) - defer conn.Close(t) - - // Bring the DUT to SYN-SENT state with a non-blocking connect. - sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], dut.Net.LocalIPv4) - if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { - t.Errorf("got connect() = %v, want EINPROGRESS", err) - } - - // Get the SYN. - tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) - if err != nil { - t.Fatalf("expected SYN: %s", err) - } - - // Send a host unreachable message. - icmpPayload := testbench.Layers{tcp.Prev(), tcp} - bytes, err := icmpPayload.ToBytes() - if err != nil { - t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) - } - - layers := conn.CreateFrame(t, nil) - layers[len(layers)-1] = &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable), - Payload: bytes, - } - conn.SendFrameStateless(t, layers) - - if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH { - t.Errorf("got connect() = %v, want EHOSTUNREACH", err) - } -} - -// TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when -// an ICMP destination unreachable message is sent in response to the inital -// SYN. -func TestTCPSynSentUnreachable6(t *testing.T) { - // Create the DUT and connection. - dut := testbench.NewDUT(t) - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6) - conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) - defer conn.Close(t) - - // Bring the DUT to SYN-SENT state with a non-blocking connect. - sa := unix.SockaddrInet6{ - Port: int(conn.SrcPort()), - ZoneId: dut.Net.RemoteDevID, - } - copy(sa.Addr[:], dut.Net.LocalIPv6) - if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { - t.Errorf("got connect() = %v, want EINPROGRESS", err) - } - - // Get the SYN. - tcp, err := conn.Expect(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) - if err != nil { - t.Fatalf("expected SYN: %s", err) - } - - // Send a host unreachable message. - icmpPayload := testbench.Layers{tcp.Prev(), tcp} - bytes, err := icmpPayload.ToBytes() - if err != nil { - t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) - } - - layers := conn.CreateFrame(t, nil) - layers[len(layers)-1] = &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable), - Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable), - Payload: bytes, - } - conn.SendFrameStateless(t, layers) - - if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH { - t.Errorf("got connect() = %v, want EHOSTUNREACH", err) - } -} - -// getConnectError gets the errno generated by the on-going connect attempt on -// fd. fd must be non-blocking and there must be a connect call to fd which -// returned EINPROGRESS before. These conditions are guaranteed in this test. -func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error { - t.Helper() - // We previously got EINPROGRESS form the connect call. We can - // handle it as explained by connect(2): - // EINPROGRESS: - // The socket is nonblocking and the connection cannot be - // completed immediately. It is possible to select(2) or poll(2) - // for completion by selecting the socket for writing. After - // select(2) indicates writability, use getsockopt(2) to read - // the SO_ERROR option at level SOL_SOCKET to determine - // whether connect() completed successfully (SO_ERROR is - // zero) or unsuccessfully (SO_ERROR is one of the usual - // error codes listed here, explaining the reason for the - // failure). - dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second) - if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { - return unix.Errno(errno) - } - return nil -} diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go deleted file mode 100644 index 14eb7d93b..000000000 --- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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_noaccept_close_rst_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.Initialize(flag.CommandLine) -} - -func TestTcpNoAcceptCloseReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - conn.Connect(t) - defer conn.Close(t) - // We need to wait for POLLIN event on listenFd to know the connection is - // established. Otherwise there could be a race when we issue the Close - // command prior to the DUT receiving the last ack of the handshake and - // it will only respond RST instead of RST+ACK. - dut.PollOne(t, listenFd, unix.POLLIN, time.Second) - dut.Close(t, listenFd) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil { - t.Fatalf("expected a RST-ACK packet but got none: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go deleted file mode 100644 index 0523887d9..000000000 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// 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_outside_the_window_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive -// that are inside or outside the TCP window. Packets that are outside the -// window should force an extra ACK, as described in RFC793 page 69: -// https://tools.ietf.org/html/rfc793#page-69 -func TestTCPOutsideTheWindow(t *testing.T) { - for _, tt := range []struct { - description string - tcpFlags header.TCPFlags - payload []testbench.Layer - seqNumOffset seqnum.Size - expectACK bool - }{ - {"SYN", header.TCPFlagSyn, nil, 0, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true}, - {"ACK", header.TCPFlagAck, nil, 0, false}, - {"FIN", header.TCPFlagFin, nil, 0, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 0, true}, - - {"SYN", header.TCPFlagSyn, nil, 1, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true}, - {"ACK", header.TCPFlagAck, nil, 1, true}, - {"FIN", header.TCPFlagFin, nil, 1, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 1, true}, - - {"SYN", header.TCPFlagSyn, nil, 2, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true}, - {"ACK", header.TCPFlagAck, nil, 2, true}, - {"FIN", header.TCPFlagFin, nil, 2, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 2, true}, - } { - t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + tt.seqNumOffset - conn.Drain(t) - // Ignore whatever incrementing that this out-of-order packet might cause - // to the AckNum. - localSeqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - conn.Send(t, testbench.TCP{ - Flags: testbench.TCPFlags(tt.tcpFlags), - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - }, tt.payload...) - timeout := time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) - if tt.expectACK && err != nil { - t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err) - } - // Data packets w/o SYN bits are always acked by Linux. Netstack ACK's data packets - // always right now. So only send a second segment and test for no ACK for packets - // with no data. - if tt.expectACK && tt.payload == nil { - // Sending another out-of-window segment immediately should not trigger - // an ACK if less than 500ms(default rate limit for out-of-window ACKs) - // has passed since the last ACK was sent. - t.Logf("sending another segment") - conn.Send(t, testbench.TCP{ - Flags: testbench.TCPFlags(tt.tcpFlags), - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - }, tt.payload...) - timeout := 3 * time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) - if err == nil { - t.Fatalf("expected no ACK packet but got one: %s", gotACK) - } - } - if !tt.expectACK && gotACK != nil { - t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK) - } - }) - } -} - -// TestAckOTWSeqInClosing tests that the DUT should send an ACK with -// the right ACK number when receiving a packet with OTW Seq number -// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69 -func TestAckOTWSeqInClosing(t *testing.T) { - for _, tt := range []struct { - description string - flags header.TCPFlags - payloads testbench.Layers - seqNumOffset seqnum.Size - expectACK bool - }{ - {"SYN", header.TCPFlagSyn, nil, 0, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true}, - {"ACK", header.TCPFlagAck, nil, 0, false}, - {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 0, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 0, false}, - - {"SYN", header.TCPFlagSyn, nil, 1, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true}, - {"ACK", header.TCPFlagAck, nil, 1, true}, - {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 1, true}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 1, true}, - - {"SYN", header.TCPFlagSyn, nil, 2, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true}, - {"ACK", header.TCPFlagAck, nil, 2, true}, - {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 2, true}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 2, true}, - } { - t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected FINACK from DUT, but got none: %s", err) - } - - // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected an ACK to our FIN, but got none: %s", err) - } - - windowSize := seqnum.Size(*gotTCP.WindowSize) + tt.seqNumOffset - conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{ - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - AckNum: seqNumForTheirFIN, - Flags: testbench.TCPFlags(tt.flags), - }}, tt.payloads...)) - - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectACK && err != nil { - t.Errorf("expected an ACK but got none: %s", err) - } - if !tt.expectACK && gotACK != nil { - t.Errorf("expected no ACK but got one: %s", gotACK) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go deleted file mode 100644 index 9054955ea..000000000 --- a/test/packetimpact/tests/tcp_paws_mechanism_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// 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_paws_mechanism_test - -import ( - "encoding/hex" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestPAWSMechanism(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - options := make([]byte, header.TCPOptionTSLength) - header.EncodeTSOption(currentTS(), 0, options) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), Options: options}) - synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("didn't get synack during handshake: %s", err) - } - parsedSynOpts := header.ParseSynOptions(synAck.Options, true) - if !parsedSynOpts.TS { - t.Fatalf("expected TSOpt from DUT, options we got:\n%s", hex.Dump(synAck.Options)) - } - tsecr := parsedSynOpts.TSVal - header.EncodeTSOption(currentTS(), tsecr, options) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - sampleData := []byte("Sample Data") - sentTSVal := currentTS() - header.EncodeTSOption(sentTSVal, tsecr, options) - // 3ms here is chosen arbitrarily to make sure we have increasing timestamps - // every time we send one, it should not cause any flakiness because timestamps - // only need to be non-decreasing. - time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected an ACK but got none: %s", err) - } - - parsedOpts := header.ParseTCPOptions(gotTCP.Options) - if !parsedOpts.TS { - t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options)) - } - if parsedOpts.TSVal < tsecr { - t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr) - } - if parsedOpts.TSEcr != sentTSVal { - t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal) - } - tsecr = parsedOpts.TSVal - lastAckNum := gotTCP.AckNum - - badTSVal := sentTSVal - 100 - header.EncodeTSOption(badTSVal, tsecr, options) - // 3ms here is chosen arbitrarily and this time.Sleep() should not cause flakiness - // due to the exact same reasoning discussed above. - time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - - gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected segment with AckNum %d but got none: %s", lastAckNum, err) - } - parsedOpts = header.ParseTCPOptions(gotTCP.Options) - if !parsedOpts.TS { - t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options)) - } - if parsedOpts.TSVal < tsecr { - t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr) - } - if parsedOpts.TSEcr != sentTSVal { - t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal) - } -} - -func currentTS() uint32 { - return uint32(time.Now().UnixNano() / 1e6) -} diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go deleted file mode 100644 index 974c15384..000000000 --- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go +++ /dev/null @@ -1,286 +0,0 @@ -// 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_recv_in_syn_sent_test - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestQueueSendInSynSentHandshake tests send behavior when the TCP state -// is SYN-SENT and the connections is finally established. -func TestQueueSendInSynSentHandshake(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - // Test blocking send. - dut.SetNonBlocking(t, socket, false) - - start := make(chan struct{}) - done := make(chan struct{}) - go func() { - defer close(done) - - close(start) - // Issue SEND call in SYN-SENT, this should be queued for - // process until the connection is established. - if _, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != unix.Errno(0) { - t.Errorf("failed to send on DUT: %s", err) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - <-start - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - select { - case <-done: - t.Fatal("expected send to be blocked in SYN-SENT") - default: - } - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) - - <-done - - // 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.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } -} - -// TestQueueRecvInSynSentHandshake tests recv behavior when the TCP state -// is SYN-SENT and the connections is finally established. -func TestQueueRecvInSynSentHandshake(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { - t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) - } - - // Test blocking read. - dut.SetNonBlocking(t, socket, false) - - start := make(chan struct{}) - done := make(chan struct{}) - go func() { - defer close(done) - - close(start) - // Issue RECEIVE call in SYN-SENT, this should be queued for - // process until the connection is established. - n, buff, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0) - if err != unix.Errno(0) { - t.Errorf("failed to recv on DUT: %s", err) - return - } - if got := buff[:n]; !bytes.Equal(got, sampleData) { - t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData)) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - <-start - - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - - // Send sample payload so that DUT can recv. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - - <-done -} - -// TestQueueSendInSynSentRST tests send behavior when the TCP state -// is SYN-SENT and an RST is sent. -func TestQueueSendInSynSentRST(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - // Test blocking send. - dut.SetNonBlocking(t, socket, false) - - start := make(chan struct{}) - done := make(chan struct{}) - go func() { - defer close(done) - - close(start) - // Issue SEND call in SYN-SENT, this should be queued for - // process until the connection is established. - n, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0) - if err != unix.ECONNREFUSED { - t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - <-start - - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - select { - case <-done: - t.Fatal("expected send to be blocked in SYN-SENT") - default: - } - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) - - <-done -} - -// TestQueueRecvInSynSentRST tests recv behavior when the TCP state -// is SYN-SENT and an RST is sent. -func TestQueueRecvInSynSentRST(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { - t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) - } - - // Test blocking read. - dut.SetNonBlocking(t, socket, false) - - start := make(chan struct{}) - done := make(chan struct{}) - go func() { - defer close(done) - - close(start) - // Issue RECEIVE call in SYN-SENT, this should be queued for - // process until the connection is established. - n, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0) - if err != unix.ECONNREFUSED { - t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - <-start - - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) - <-done -} diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go deleted file mode 100644 index 5a60bf712..000000000 --- a/test/packetimpact/tests/tcp_rack_test.go +++ /dev/null @@ -1,404 +0,0 @@ -// 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_rack_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -const ( - // payloadSize is the size used to send packets. - payloadSize = header.TCPDefaultMSS - - // simulatedRTT is the time delay between packets sent and acked to - // increase the RTT. - simulatedRTT = 30 * time.Millisecond - - // numPktsForRTT is the number of packets sent and acked to establish - // RTT. - numPktsForRTT = 10 -) - -func createSACKConnection(t *testing.T) (testbench.DUT, testbench.TCPIPv4, int32, int32) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - - // Enable SACK. - opts := make([]byte, 40) - optsOff := 0 - optsOff += header.EncodeNOP(opts[optsOff:]) - optsOff += header.EncodeNOP(opts[optsOff:]) - optsOff += header.EncodeSACKPermittedOption(opts[optsOff:]) - - conn.ConnectWithOptions(t, opts[:optsOff]) - acceptFd, _ := dut.Accept(t, listenFd) - return dut, conn, acceptFd, listenFd -} - -func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, acceptFd, listenFd int32) { - dut.Close(t, acceptFd) - dut.Close(t, listenFd) - conn.Close(t) -} - -func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) { - info := dut.GetSockOptTCPInfo(t, acceptFd) - return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond -} - -func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, numPkts int, acceptFd int32, sendACK bool) time.Time { - seqNum1 := *conn.RemoteSeqNum(t) - payload := make([]byte, payloadSize) - var lastSent time.Time - for i, sn := 0, seqNum1; i < numPkts; i++ { - lastSent = time.Now() - dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second) - if err != nil { - t.Fatalf("Expect #%d: %s", i+1, err) - continue - } - if gotOne == nil { - t.Fatalf("#%d: expected a packet within a second but got none", i+1) - } - sn.UpdateForward(seqnum.Size(payloadSize)) - - if sendACK { - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))}) - } - } - return lastSent -} - -// TestRACKTLPAllPacketsLost tests TLP when an entire flight of data is lost. -func TestRACKTLPAllPacketsLost(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 5 - lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // Probe Timeout (PTO) should be two times RTT. Check that the last - // packet is retransmitted after probe timeout. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - pto := rtt * 2 - // We expect the 5th packet (the last unacknowledged packet) to be - // retransmitted. - tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s %v %v", err, rtt, pto) - } - diff := time.Now().Sub(lastSent) - if diff < pto { - t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKTLPLost tests TLP when there are tail losses. -// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.4 -func TestRACKTLPLost(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 10 - lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // Cumulative ACK for #[1-5] packets. - ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize)) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))}) - - // Probe Timeout (PTO) should be two times RTT. Check that the last - // packet is retransmitted after probe timeout. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - pto := rtt * 2 - // We expect the 10th packet (the last unacknowledged packet) to be - // retransmitted. - tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - diff := time.Now().Sub(lastSent) - if diff < pto { - t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithSACK tests that RACK marks the packets as lost after receiving -// the ACK for retransmitted packets. -// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1 -func TestRACKWithSACK(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 3 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // SACK for #2 packet. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(payloadSize)) - end := start.Add(seqnum.Size(payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - // RACK marks #1 packet as lost after RTT+reorderWindow(RTT/4) and - // retransmits it. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - time.Sleep(simulatedRTT) - // ACK for #1 packet. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))}) - - // RACK considers transmission times of the packets to mark them lost. - // As the 3rd packet was sent before the retransmitted 1st packet, RACK - // marks it as lost and retransmits it.. - expectedSeqNum := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: expectedSeqNum}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithoutReorder tests that without reordering RACK will retransmit the -// lost packets after reorder timer expires. -func TestRACKWithoutReorder(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 4 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // SACK for [3,4] packets. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(2 * payloadSize)) - end := start.Add(seqnum.Size(2 * payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // RACK marks #1 and #2 packets as lost and retransmits both after - // RTT + reorderWindow. The reorderWindow initially will be a small - // fraction of RTT. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - for i, sn := 0, seqNum1; i < 2; i++ { - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - sn.UpdateForward(seqnum.Size(payloadSize)) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithReorder tests that RACK will retransmit segments when there is -// reordering in the connection and reorder timer expires. -func TestRACKWithReorder(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 4 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // SACK in reverse order for the connection to detect reorder. - var start seqnum.Value - var end seqnum.Value - for i := 0; i < numPkts-1; i++ { - sackBlock := make([]byte, 40) - sbOff := 0 - start = seqNum1.Add(seqnum.Size((numPkts - i - 1) * payloadSize)) - end = start.Add(seqnum.Size((i + 1) * payloadSize)) - sackBlock = make([]byte, 40) - sbOff = 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - } - - // Send a DSACK block indicating both original and retransmitted - // packets are received, RACK will increase the reordering window on - // every DSACK. - dsackBlock := make([]byte, 40) - dbOff := 0 - start = seqNum1 - end = start.Add(seqnum.Size(2 * payloadSize)) - dbOff += header.EncodeNOP(dsackBlock[dbOff:]) - dbOff += header.EncodeNOP(dsackBlock[dbOff:]) - dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, dsackBlock[dbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]}) - - seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize)) - sendTime := time.Now() - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // Send SACK for [2-5] packets. - sackBlock := make([]byte, 40) - sbOff := 0 - start = seqNum1.Add(seqnum.Size(payloadSize)) - end = start.Add(seqnum.Size(3 * payloadSize)) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // Expect the retransmission of #1 packet after RTT+ReorderWindow. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - diff := time.Now().Sub(sendTime) - if diff < rtt { - t.Fatalf("expected payload was received too sonn, within RTT") - } - - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithLostRetransmission tests that RACK will not enter RTO when a -// retransmitted segment is lost and enters fast recovery. -func TestRACKWithLostRetransmission(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 5 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // SACK for [2-5] packets. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(payloadSize)) - end := start.Add(seqnum.Size(4 * payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // RACK marks #1 packet as lost and retransmits it after - // RTT + reorderWindow. The reorderWindow is bounded between a small - // fraction of RTT and 1 RTT. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Send #6 packet. - payload := make([]byte, payloadSize) - dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1 + 5*payloadSize))}, time.Second) - if err != nil { - t.Fatalf("Expect #6: %s", err) - } - if gotOne == nil { - t.Fatalf("#6: expected a packet within a second but got none") - } - - // SACK for [2-6] packets. - sackBlock1 := make([]byte, 40) - start = seqNum1.Add(seqnum.Size(payloadSize)) - end = start.Add(seqnum.Size(5 * payloadSize)) - sbOff1 := 0 - sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:]) - sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:]) - sbOff1 += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock1[sbOff1:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]}) - - // Expect re-retransmission of #1 packet without entering an RTO. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check the congestion control state. - info := dut.GetSockOptTCPInfo(t, acceptFd) - if info.CaState != linux.TCP_CA_Recovery { - t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState) - } - - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go deleted file mode 100644 index f121d44eb..000000000 --- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// 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_rcv_buf_space_test - -import ( - "context" - "flag" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestReduceRecvBuf tests that a packet within window is still dropped -// if the available buffer space drops below the size of the incoming -// segment. -func TestReduceRecvBuf(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - // Set a small receive buffer for the test. - const rcvBufSz = 4096 - dut.SetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF, rcvBufSz) - - // Retrieve the actual buffer. - bufSz := dut.GetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF) - - // Generate a payload of 1 more than the actual buffer size used by the - // DUT. - sampleData := testbench.GenerateRandomPayload(t, int(bufSz)+1) - // Send and receive sample data to the dut. - const pktSize = 1400 - for payload := sampleData; len(payload) != 0; { - payloadBytes := pktSize - if l := len(payload); l < payloadBytes { - payloadBytes = l - } - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...) - payload = payload[payloadBytes:] - } - - // First read should read < len(sampleData) - if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), 0); ret == -1 || int(ret) == len(sampleData) { - t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, int32(len(sampleData)), ret, err) - } - - // Second read should return EAGAIN as the last segment should have been - // dropped due to it exceeding the receive buffer space available in the - // socket. - if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), unix.MSG_DONTWAIT); got != nil || ret != -1 || err != unix.EAGAIN { - t.Fatalf("expected no packets but got: %s", got) - } -} diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go deleted file mode 100644 index d3fb789f4..000000000 --- a/test/packetimpact/tests/tcp_retransmits_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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_retransmits_test - -import ( - "bytes" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func getRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rto time.Duration) { - info := dut.GetSockOptTCPInfo(t, acceptFd) - return time.Duration(info.RTO) * time.Microsecond -} - -// TestRetransmits tests retransmits occur at exponentially increasing -// time intervals. -func TestRetransmits(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. - // This is to reduce the test run-time from the default initial RTO of 1s. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip this data send/recv which is solely to estimate RTO. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // Wait for the DUT to receive the data, thus ensuring that the stack has - // estimated RTO before we query RTO via TCP_INFO. - if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) - } - - const timeoutCorrection = time.Second - const diffCorrection = 200 * time.Millisecond - rto := getRTO(t, dut, acceptFd) - - dut.Send(t, acceptFd, sampleData, 0) - seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Expect retransmits of the same segment. - for i := 0; i < 5; i++ { - startTime := time.Now() - rto = getRTO(t, dut, acceptFd) - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { - t.Fatalf("expected payload was not received within %s loop %d err %s", rto+timeoutCorrection, i, err) - } - if diff := time.Since(startTime); diff+diffCorrection < rto { - t.Fatalf("retransmit came sooner got: %s want: >= %s probe %d", diff+diffCorrection, rto, i) - } - } -} diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go deleted file mode 100644 index 64b7288fb..000000000 --- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// 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_send_window_sizes_piggyback_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.Initialize(flag.CommandLine) -} - -// TestSendWindowSizesPiggyback tests cases where segment sizes are close to -// sender window size and checks for ACK piggybacking for each of those case. -func TestSendWindowSizesPiggyback(t *testing.T) { - sampleData := []byte("Sample Data") - segmentSize := uint16(len(sampleData)) - // Advertise receive window sizes that are lesser, equal to or greater than - // enqueued segment size and check for segment transmits. The test attempts - // to enqueue a segment on the dut before acknowledging previous segment and - // lets the dut piggyback any ACKs along with the enqueued segment. - for _, tt := range []struct { - description string - windowSize uint16 - expectedPayload1 []byte - expectedPayload2 []byte - enqueue bool - }{ - // Expect the first segment to be split as it cannot be accomodated in - // the sender window. This means we need not enqueue a new segment after - // the first segment. - {"WindowSmallerThanSegment", segmentSize - 1, sampleData[:(segmentSize - 1)], sampleData[(segmentSize - 1):], false /* enqueue */}, - - {"WindowEqualToSegment", segmentSize, sampleData, sampleData, true /* enqueue */}, - - // Expect the second segment to not be split as its size is greater than - // the available sender window size. The segments should not be split - // when there is pending unacknowledged data and the segment-size is - // greater than available sender window. - {"WindowGreaterThanSegment", segmentSize + 1, sampleData, sampleData, true /* enqueue */}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - expectedTCP := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)} - - dut.Send(t, acceptFd, sampleData, 0) - expectedPayload := testbench.Payload{Bytes: tt.expectedPayload1} - if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Expect any enqueued segment to be transmitted by the dut along with - // piggybacked ACK for our data. - - if tt.enqueue { - // Enqueue a segment for the dut to transmit. - dut.Send(t, acceptFd, sampleData, 0) - } - - // Send ACK for the previous segment along with data for the dut to - // receive and ACK back. Sending this ACK would make room for the dut - // to transmit any enqueued segment. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData}) - - // Expect the dut to piggyback the ACK for received data along with - // the segment enqueued for transmit. - expectedPayload = testbench.Payload{Bytes: tt.expectedPayload2} - if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_syncookie_test.go b/test/packetimpact/tests/tcp_syncookie_test.go deleted file mode 100644 index 6be09996b..000000000 --- a/test/packetimpact/tests/tcp_syncookie_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2021 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_syncookie_test - -import ( - "flag" - "fmt" - "math" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPSynCookie tests for ACK handling for connections in SYNRCVD state -// connections with and without syncookies. It verifies if the passive open -// connection is indeed using syncookies before proceeding. -func TestTCPSynCookie(t *testing.T) { - dut := testbench.NewDUT(t) - for _, tt := range []struct { - accept bool - flags header.TCPFlags - }{ - {accept: true, flags: header.TCPFlagAck}, - {accept: true, flags: header.TCPFlagAck | header.TCPFlagPsh}, - {accept: false, flags: header.TCPFlagAck | header.TCPFlagSyn}, - {accept: true, flags: header.TCPFlagAck | header.TCPFlagFin}, - {accept: false, flags: header.TCPFlagAck | header.TCPFlagRst}, - {accept: false, flags: header.TCPFlagRst}, - } { - t.Run(fmt.Sprintf("flags=%s", tt.flags), func(t *testing.T) { - // Make a copy before parallelizing the test and refer to that - // within the test. Otherwise, the test reference could be pointing - // to an incorrect variant based on how it is scheduled. - test := tt - - t.Parallel() - - // Listening endpoint accepts one more connection than the listen - // backlog. Listener starts using syncookies when it sees a new SYN - // and has backlog size of connections in SYNRCVD state. Keep the - // listen backlog 1, so that the test can define 2 connections - // without and with using syncookies. - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - - var withoutSynCookieConn testbench.TCPIPv4 - var withSynCookieConn testbench.TCPIPv4 - - for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} { - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - } - defer withoutSynCookieConn.Close(t) - defer withSynCookieConn.Close(t) - - // Setup the 2 connections in SYNRCVD state and verify if one of the - // connection is indeed using syncookies by checking for absence of - // SYNACK retransmits. - for _, c := range []struct { - desc string - conn *testbench.TCPIPv4 - expectRetransmit bool - }{ - {desc: "without syncookies", conn: &withoutSynCookieConn, expectRetransmit: true}, - {desc: "with syncookies", conn: &withSynCookieConn, expectRetransmit: false}, - } { - t.Run(c.desc, func(t *testing.T) { - // Expect dut connection to have transitioned to SYNRCVD state. - c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := c.conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYNACK, but got %s", err) - } - - // If the DUT listener is using syn cookies, it will not retransmit SYNACK. - got, err := c.conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*c.conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second) - if c.expectRetransmit && err != nil { - t.Fatalf("expected retransmitted SYNACK, but got %s", err) - } - if !c.expectRetransmit && err == nil { - t.Fatalf("expected no retransmitted SYNACK, but got %s", got) - } - }) - } - - // Check whether ACKs with the given flags completes the handshake. - for _, c := range []struct { - desc string - conn *testbench.TCPIPv4 - }{ - {desc: "with syncookies", conn: &withSynCookieConn}, - {desc: "without syncookies", conn: &withoutSynCookieConn}, - } { - t.Run(c.desc, func(t *testing.T) { - pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: math.MaxInt16}}, 0 /*timeout*/) - if got, want := len(pfds), 0; got != want { - t.Fatalf("dut.Poll(...) = %d, want = %d", got, want) - } - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)}, samplePayload) - pfds = dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: unix.POLLIN}}, time.Second) - want := 0 - if test.accept { - want = 1 - } - if got := len(pfds); got != want { - t.Fatalf("got dut.Poll(...) = %d, want = %d", got, want) - } - // Accept the connection to enable poll on any subsequent connection. - if test.accept { - fd, _ := dut.Accept(t, listenFD) - if test.flags.Contains(header.TCPFlagFin) { - if dut.Uname.IsLinux() { - dut.PollOne(t, fd, unix.POLLIN|unix.POLLRDHUP, time.Second) - } else { - // TODO(gvisor.dev/issue/6015): Notify POLLIN|POLLRDHUP on incoming FIN. - dut.PollOne(t, fd, unix.POLLIN, time.Second) - } - } - got := dut.Recv(t, fd, int32(len(sampleData)), 0) - if diff := cmp.Diff(got, sampleData); diff != "" { - t.Fatalf("dut.Recv: data mismatch (-want +got):\n%s", diff) - } - dut.Close(t, fd) - } - }) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go deleted file mode 100644 index 3346d43c4..000000000 --- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// 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_syn_reset_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.Initialize(flag.CommandLine) -} - -// TestTCPSynRcvdReset tests transition from SYN-RCVD to CLOSED. -func TestTCPSynRcvdReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - // Expect dut connection to have transitioned to SYN-RCVD state. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - - // Expect the connection to have transitioned SYN-RCVD to CLOSED. - // - // Retransmit the ACK a few times to give time for the DUT to transition to - // CLOSED. We cannot use TCP_INFO to lookup the state as this is a passive - // DUT connection. - for i := 0; i < 5; i++ { - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Logf("retransmit%d ACK as we did not get the expected RST, %s", i, err) - continue - } - return - } - t.Fatal("did not receive a TCP RST") -} diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go deleted file mode 100644 index fe53e7061..000000000 --- a/test/packetimpact/tests/tcp_synsent_reset_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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_synsent_reset_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// dutSynSentState sets up the dut connection in SYN-SENT state. -func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, int32, uint16, uint16) { - t.Helper() - - dut := testbench.NewDUT(t) - - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - port := uint16(9001) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) - - sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], dut.Net.LocalIPv4) - // Bring the dut to SYN-SENT state with a non-blocking connect. - dut.Connect(t, clientFD, &sa) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN\n") - } - - return &dut, &conn, clientFD, port, clientPort -} - -// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition. -func TestTCPSynSentReset(t *testing.T) { - dut, conn, fd, _, _ := dutSynSentState(t) - defer conn.Close(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) - // Expect the connection to have closed. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } - info := dut.GetSockOptTCPInfo(t, fd) - if got, want := uint32(info.State), linux.TCP_CLOSE; got != want { - t.Fatalf("got %d want %d", got, want) - } -} - -// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED -// transitions. -func TestTCPSynSentRcvdReset(t *testing.T) { - dut, c, fd, remotePort, clientPort := dutSynSentState(t) - defer c.Close(t) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort}) - defer conn.Close(t) - // Initiate new SYN connection with the same port pair - // (simultaneous open case), expect the dut connection to move to - // SYN-RCVD state - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK %s\n", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - // Expect the connection to have transitioned SYN-RCVD to CLOSED. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } - info := dut.GetSockOptTCPInfo(t, fd) - if got, want := uint32(info.State), linux.TCP_CLOSE; got != want { - t.Fatalf("got %d want %d", got, want) - } -} diff --git a/test/packetimpact/tests/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go deleted file mode 100644 index 89037f0a4..000000000 --- a/test/packetimpact/tests/tcp_timewait_reset_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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_timewait_reset_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.Initialize(flag.CommandLine) -} - -// TestTimeWaitReset tests handling of RST when in TIME_WAIT state. -func TestTimeWaitReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Close(t, acceptFD) - - _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Send a FIN, DUT should transition to TIME_WAIT from FIN_WAIT2. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK for our FIN: %s", err) - } - - // Send a RST, the DUT should transition to CLOSED from TIME_WAIT. - // This is the default Linux behavior, it can be changed to ignore RSTs via - // sysctl net.ipv4.tcp_rfc1337. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // The DUT should reply with RST to our ACK as the state should have - // transitioned to CLOSED. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected a RST: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go deleted file mode 100644 index 389bfc629..000000000 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go +++ /dev/null @@ -1,274 +0,0 @@ -// 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_unacc_seq_ack_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestEstablishedUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - restoreSeq bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: true, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: false, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: false, restoreSeq: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - dut.Accept(t, listenFD) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected ack %s", err) - } - windowSize := seqnum.Size(*gotTCP.WindowSize) - - origSeq := *conn.LocalSeqNum(t) - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload) - if tt.restoreSeq { - // Restore the local sequence number to ensure that the incoming - // ACK matches the TCP layer state. - *conn.LocalSeqNum(t) = origSeq - } - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Fatalf("expected an ack but got none: %s", err) - } - if err == nil && !tt.expectAck && gotAck != nil { - t.Fatalf("expected no ack but got one: %s", gotAck) - } - }) - } -} - -func TestPassiveCloseUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Send a FIN to DUT to intiate the passive close. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)}) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err) - } - windowSize := seqnum.Size(*gotTCP.WindowSize) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload) - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Errorf("expected an ack but got none: %s", err) - } - if err == nil && !tt.expectAck && gotAck != nil { - t.Errorf("expected no ack but got one: %s", gotAck) - } - - // Now let's verify DUT is indeed in CLOSE_WAIT - dut.Close(t, acceptFD) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(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(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Send some extra data to DUT - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, samplePayload) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected DUT to send an RST: %s", err) - } - }) - } -} - -func TestActiveCloseUnaccpSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - restoreSeq bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, restoreSeq: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - // Get to FIN_WAIT2 - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - sendUnaccSeqAck := func(state string) { - t.Helper() - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - origSeq := *conn.LocalSeqNum(t) - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)), samplePayload) - if tt.restoreSeq { - // Restore the local sequence number to ensure that the - // incoming ACK matches the TCP layer state. - *conn.LocalSeqNum(t) = origSeq - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ack in %s state, but got none: %s", state, err) - } - } - - sendUnaccSeqAck("FIN_WAIT2") - - // Send a FIN to DUT to get to TIME_WAIT - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK for our fin and DUT should enter TIME_WAIT: %s", err) - } - - sendUnaccSeqAck("TIME_WAIT") - }) - } -} - -func TestSimultaneousCloseUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected a FIN: %s", err) - } - // Do not ack the FIN from DUT so that we get to CLOSING. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - origSeq := uint32(*conn.LocalSeqNum(t)) - // Send a segment with OTW Seq / unacc ACK. - tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)) - if tt.description == "OTWSeq" { - // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that - // we stay in CLOSING. - tcp.AckNum = seqNumForTheirFIN - } - conn.Send(t, tcp, samplePayload) - - got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Errorf("expected an ack in CLOSING state, but got none: %s", err) - } - if !tt.expectAck && got != nil { - t.Errorf("expected no ack in CLOSING state, but got one: %s", got) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go deleted file mode 100644 index ef38bd738..000000000 --- a/test/packetimpact/tests/tcp_user_timeout_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// 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_user_timeout_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.Initialize(flag.CommandLine) -} - -func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) { - sampleData := make([]byte, 100) - for i := range sampleData { - sampleData[i] = uint8(i) - } - conn.Drain(t) - dut.Send(t, fd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected data but got none: %w", err) - } -} - -func sendFIN(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) { - dut.Close(t, fd) -} - -func TestTCPUserTimeout(t *testing.T) { - for _, tt := range []struct { - description string - userTimeout time.Duration - sendDelay time.Duration - }{ - {"NoUserTimeout", 0, 3 * time.Second}, - {"ACKBeforeUserTimeout", 5 * time.Second, 4 * time.Second}, - {"ACKAfterUserTimeout", 5 * time.Second, 7 * time.Second}, - } { - for _, ttf := range []struct { - description string - f func(_ *testing.T, _ *testbench.TCPIPv4, _ *testbench.DUT, fd int32) - }{ - {"AfterPayload", sendPayload}, - {"AfterFIN", sendFIN}, - } { - t.Run(tt.description+ttf.description, func(t *testing.T) { - // Create a socket, listen, TCP handshake, and accept. - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - if tt.userTimeout != 0 { - dut.SetSockOptInt(t, acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds())) - } - - ttf.f(t, &conn, &dut, acceptFD) - - time.Sleep(tt.sendDelay) - conn.Drain(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - // If TCP_USER_TIMEOUT was set and the above delay was longer than the - // TCP_USER_TIMEOUT then the DUT should send a RST in response to the - // testbench's packet. - expectRST := tt.userTimeout != 0 && tt.sendDelay > tt.userTimeout - expectTimeout := 5 * time.Second - got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, expectTimeout) - if expectRST && err != nil { - t.Errorf("expected RST packet within %s but got none: %s", expectTimeout, err) - } - if !expectRST && got != nil { - t.Errorf("expected no RST packet within %s but got one: %s", expectTimeout, got) - } - }) - } - } -} diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go deleted file mode 100644 index 0d65a2ea2..000000000 --- a/test/packetimpact/tests/tcp_window_shrink_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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_window_shrink_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.Initialize(flag.CommandLine) -} - -func TestWindowShrink(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Send(t, acceptFd, sampleData, 0) - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - // We close our receiving window here - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - dut.Send(t, acceptFd, []byte("Sample Data"), 0) - // Note: There is another kind of zero-window probing which Windows uses (by sending one - // new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change - // the following lines. - expectedRemoteSeqNum := *conn.RemoteSeqNum(t) - 1 - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", expectedRemoteSeqNum, err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go deleted file mode 100644 index bd33a2a03..000000000 --- a/test/packetimpact/tests/tcp_zero_receive_window_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// 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_zero_receive_window_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroReceiveWindow tests if the DUT sends a zero receive window eventually. -func TestZeroReceiveWindow(t *testing.T) { - for _, payloadLen := range []int{64, 512, 1024} { - t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - fillRecvBuffer(t, &conn, &dut, acceptFd, payloadLen) - }) - } -} - -func fillRecvBuffer(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, acceptFd int32, payloadLen int) { - // Expect the DUT to eventually advertise zero receive window. - // The test would timeout otherwise. - for readOnce := false; ; { - samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // Read once to trigger the subsequent window update from the - // DUT to grow the right edge of the receive window from what - // was advertised in the SYN-ACK. This ensures that we test - // for the full default buffer size (1MB on gVisor at the time - // of writing this comment), thus testing for cases when the - // scaled receive window size ends up > 65535 (0xffff). - if !readOnce { - if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen) - } - readOnce = true - } - windowSize := *gotTCP.WindowSize - t.Logf("got window size = %d", windowSize) - if windowSize == 0 { - break - } - if payloadLen > int(windowSize) { - payloadLen = int(windowSize) - } - } -} - -func TestZeroToNonZeroWindowUpdate(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("didn't get synack during handshake: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - mss := header.ParseSynOptions(synAck.Options, true).MSS - fillRecvBuffer(t, &conn, &dut, acceptFd, int(mss)) - - // Read < mss worth of data from the receive buffer and expect the DUT to - // not send a non-zero window update. - payloadLen := mss - 1 - if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != int(payloadLen) { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen) - } - // Send a zero-window-probe to force an ACK from the receiver with any - // window updates. - conn.Send(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagAck)}) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - if windowSize := *gotTCP.WindowSize; windowSize != 0 { - t.Fatalf("got non zero window = %d", windowSize) - } - - // Now, ensure that the DUT eventually sends non-zero window update. - seqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1)) - ackNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - recvCheckWindowUpdate := func(readLen int) uint16 { - if got := dut.Recv(t, acceptFd, int32(readLen), 0); len(got) != readLen { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, readLen, len(got), readLen) - } - conn.Send(t, testbench.TCP{SeqNum: seqNum, Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: make([]byte, 1)}) - gotTCP, err := conn.Expect(t, testbench.TCP{AckNum: ackNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - return *gotTCP.WindowSize - } - - if !dut.Uname.IsLinux() { - if win := recvCheckWindowUpdate(1); win == 0 { - t.Fatal("expected non-zero window update") - } - } else { - // Linux stack takes additional socket reads to send out window update, - // its a function of sysctl_tcp_rmem among other things. - // https://github.com/torvalds/linux/blob/7acac4b3196/net/ipv4/tcp_input.c#L687 - for { - if win := recvCheckWindowUpdate(int(payloadLen)); win != 0 { - break - } - } - } -} - -// TestNonZeroReceiveWindow tests for the DUT to never send a zero receive -// window when the data is being read from the socket buffer. -func TestNonZeroReceiveWindow(t *testing.T) { - for _, payloadLen := range []int{64, 512, 1024} { - t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - var rcvWindow uint16 - initRcv := false - // This loop keeps a running rcvWindow value from the initial ACK for the data - // we sent. Once we have received ACKs with non-zero receive windows, we break - // the loop. - for { - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen) - } - if *gotTCP.WindowSize == 0 { - t.Fatalf("expected non-zero receive window.") - } - if !initRcv { - rcvWindow = uint16(*gotTCP.WindowSize) - initRcv = true - } - if rcvWindow <= uint16(payloadLen) { - break - } - rcvWindow -= uint16(payloadLen) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go deleted file mode 100644 index 22b17a39e..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// 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_zero_window_probe_retransmit_test - -import ( - "bytes" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbeRetransmit tests retransmits of zero window probes -// to be sent at exponentially inreasing time intervals. -func TestZeroWindowProbeRetransmit(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check for the dut to keep the connection alive as long as the zero window - // probes are acknowledged. Check if the zero window probes are sent at - // exponentially increasing intervals. The timeout intervals are function - // of the recorded first zero probe transmission duration. - // - // Advertize zero receive window along with a payload. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(0)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // Wait for the payload to be received by the DUT, which is also an - // indication of receive of the peer window advertisement. - if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) - } - - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - - var prev time.Duration - // Expect the dut to keep the connection alive as long as the remote is - // acknowledging the zero-window probes. - for i := 1; i <= 5; i++ { - start := time.Now() - // Expect zero-window probe with a timeout which is a function of the typical - // first retransmission time. The retransmission times is supposed to - // exponentially increase. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Duration(i)*time.Second); err != nil { - t.Fatalf("%d: expected a probe with sequence number %d: %s", i, probeSeq, err) - } - if i == 1 { - // Skip the first probe as computing transmit time for that is - // non-deterministic because of the arbitrary time taken for - // the dut to receive a send command and issue a send. - continue - } - - // Check if the time taken to receive the probe from the dut is - // increasing exponentially. To avoid flakes, use a correction - // factor for the expected duration which accounts for any - // scheduling non-determinism. - const timeCorrection = 200 * time.Millisecond - got := time.Since(start) - if want := (2 * prev) - timeCorrection; prev != 0 && got < want { - t.Errorf("got zero probe %d after %s, want >= %s", i, got, want) - } - prev = got - // Acknowledge the zero-window probes from the dut. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - } - // Advertize non-zero window. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the dut to recover and transmit data. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go deleted file mode 100644 index 8b90fcbe9..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// 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_zero_window_probe_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.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbe tests few cases of zero window probing over the -// same connection. -func TestZeroWindowProbe(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - start := time.Now() - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - sendTime := time.Now().Sub(start) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - - // Test 1: Check for receive of a zero window probe, record the duration for - // probe to be sent. - // - // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - // Expected sequence number of the zero window probe. - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - // Expected ack number of the ACK for the probe. - ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - - // Expect there are no zero-window probes sent until there is data to be sent out - // from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil { - t.Fatalf("unexpected packet with sequence number %d: %s", probeSeq, err) - } - - start = time.Now() - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - // Expect zero-window probe from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err) - } - // Expect the probe to be sent after some time. Compare against the previous - // time recorded when the dut immediately sends out data on receiving the - // send command. - if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime { - t.Fatalf("expected the first probe to be sent out after retransmission interval, got %s want > %s", startProbeDuration, sendTime) - } - - // Test 2: Check if the dut recovers on advertizing non-zero receive window. - // and sends out the sample payload after the send window opens. - // - // Advertize non-zero window to the dut and ack the zero window probe. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the dut to recover and transmit data. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Test 3: Sanity check for dut's processing of a similar probe it sent. - // Check if the dut responds as we do for a similar probe sent to it. - // Basically with sequence number to one byte behind the unacknowledged - // sequence number. - p := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with ack number: %d: %s", p, err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go deleted file mode 100644 index 1ce4d22b7..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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_zero_window_probe_usertimeout_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.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are -// retransmitting zero window probes. -func TestZeroWindowProbeUserTimeout(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - - // Test 1: Check for receive of a zero window probe, record the duration for - // probe to be sent. - // - // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - // Expected sequence number of the zero window probe. - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - start := time.Now() - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - // Expect zero-window probe from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err) - } - // Record the duration for first probe, the dut sends the zero window probe after - // a retransmission time interval. - startProbeDuration := time.Now().Sub(start) - - // Test 2: Check if the dut times out the connection by honoring usertimeout - // when the dut is sending zero-window probes. - // - // Reduce the retransmit timeout. - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds())) - // Advertize zero window again. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - // Ask the dut to send out data that would trigger zero window probe retransmissions. - dut.Send(t, acceptFd, sampleData, 0) - - // Wait for the connection to timeout after multiple zero-window probe retransmissions. - time.Sleep(8 * startProbeDuration) - - // Expect the connection to have timed out and closed which would cause the dut - // to reply with a RST to the ACK we send. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } -} diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go deleted file mode 100644 index f4ae00a81..000000000 --- a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// 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 udp_any_addr_recv_unicast_test - -import ( - "flag" - "net" - "testing" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestAnyRecvUnicastUDP(t *testing.T) { - dut := testbench.NewDUT(t) - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, boundFD) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) - conn.SendIP( - t, - testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(dut.Net.RemoteIPv4))}, - testbench.UDP{}, - &testbench.Payload{Bytes: payload}, - ) - got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } -} diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go deleted file mode 100644 index f63cfcc9a..000000000 --- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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 udp_discard_mcast_source_addr_test - -import ( - "context" - "flag" - "fmt" - "net" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -var oneSecond = unix.Timeval{Sec: 1, Usec: 0} - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) { - dut := testbench.NewDUT(t) - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4) - defer dut.Close(t, remoteFD) - dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - for _, mcastAddr := range []net.IP{ - net.IPv4allsys, - net.IPv4allrouter, - net.IPv4(224, 0, 1, 42), - net.IPv4(232, 1, 2, 3), - } { - t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) { - conn.SendIP( - t, - testbench.IPv4{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To4()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: []byte("test payload")}, - ) - - ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) - } - }) - } -} - -func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) { - dut := testbench.NewDUT(t) - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv6) - defer dut.Close(t, remoteFD) - dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - for _, mcastAddr := range []net.IP{ - net.IPv6interfacelocalallnodes, - net.IPv6linklocalallnodes, - net.IPv6linklocalallrouters, - net.ParseIP("ff01::42"), - net.ParseIP("ff02::4242"), - } { - t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) { - conn.SendIPv6( - t, - testbench.IPv6{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To16()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: []byte("test payload")}, - ) - ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) - } - }) - } -} diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go deleted file mode 100644 index 556cee1d9..000000000 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// 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 udp_icmp_error_propagation_test - -import ( - "context" - "flag" - "fmt" - "net" - "sync" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type connectionMode bool - -func (c connectionMode) String() string { - if c { - return "Connected" - } - return "Connectionless" -} - -type icmpError int - -const ( - portUnreachable icmpError = iota - timeToLiveExceeded -) - -func (e icmpError) String() string { - switch e { - case portUnreachable: - return "PortUnreachable" - case timeToLiveExceeded: - return "TimeToLiveExpired" - } - return "Unknown ICMP error" -} - -func (e icmpError) ToICMPv4(payload []byte) *testbench.ICMPv4 { - switch e { - case portUnreachable: - return &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4PortUnreachable), - Payload: payload, - } - case timeToLiveExceeded: - return &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded), - Code: testbench.ICMPv4Code(header.ICMPv4TTLExceeded), - Payload: payload, - } - } - return nil -} - -type errorDetection struct { - name string - useValidConn bool - f func(context.Context, *testing.T, testData) -} - -type testData struct { - dut *testbench.DUT - conn *testbench.UDPIPv4 - remoteFD int32 - remotePort uint16 - cleanFD int32 - cleanPort uint16 - wantErrno unix.Errno -} - -// wantErrno computes the errno to expect given the connection mode of a UDP -// socket and the ICMP error it will receive. -func wantErrno(c connectionMode, icmpErr icmpError) unix.Errno { - if c && icmpErr == portUnreachable { - return unix.ECONNREFUSED - } - return unix.Errno(0) -} - -// sendICMPError sends an ICMP error message in response to a UDP datagram. -func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp *testbench.UDP) { - t.Helper() - - ip, ok := udp.Prev().(*testbench.IPv4) - if !ok { - t.Fatalf("expected %s to be IPv4", udp.Prev()) - } - if icmpErr == timeToLiveExceeded { - *ip.TTL = 1 - // Let serialization recalculate the checksum since we set the TTL - // to 1. - ip.Checksum = nil - } - - icmpPayload := testbench.Layers{ip, udp} - bytes, err := icmpPayload.ToBytes() - if err != nil { - t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) - } - - layers := conn.CreateFrame(t, nil) - layers[len(layers)-1] = icmpErr.ToICMPv4(bytes) - conn.SendFrameStateless(t, layers) -} - -// testRecv tests observing the ICMP error through the recv unix. A packet -// is sent to the DUT, and if wantErrno is non-zero, then the first recv should -// fail and the second should succeed. Otherwise if wantErrno is zero then the -// first recv should succeed immediately. -func testRecv(ctx context.Context, t *testing.T, d testData) { - t.Helper() - - // Check that receiving on the clean socket works. - d.conn.Send(t, testbench.UDP{DstPort: &d.cleanPort}) - d.dut.Recv(t, d.cleanFD, 100, 0) - - d.conn.Send(t, testbench.UDP{}) - - if d.wantErrno != unix.Errno(0) { - ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0) - if ret != -1 { - t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno) - } - if err != d.wantErrno { - t.Fatalf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno) - } - } - - d.dut.Recv(t, d.remoteFD, 100, 0) -} - -// testSendTo tests observing the ICMP error through the send syscall. If -// wantErrno is non-zero, the first send should fail and a subsequent send -// should succeed; while if wantErrno is zero then the first send should just -// succeed. -func testSendTo(ctx context.Context, t *testing.T, d testData) { - // Check that sending on the clean socket works. - d.dut.SendTo(t, d.cleanFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet from clean socket on DUT: %s", err) - } - - if d.wantErrno != unix.Errno(0) { - ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - - if ret != -1 { - t.Fatalf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno) - } - if err != d.wantErrno { - t.Fatalf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno) - } - } - - d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet as expected: %s", err) - } -} - -func testSockOpt(_ context.Context, t *testing.T, d testData) { - // Check that there's no pending error on the clean socket. - if errno := unix.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != unix.Errno(0) { - t.Fatalf("unexpected error (%[1]d) %[1]v on clean socket", errno) - } - - if errno := unix.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno { - t.Fatalf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno) - } - - // Check that after clearing socket error, sending doesn't fail. - d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet as expected: %s", err) - } -} - -// TestUDPICMPErrorPropagation tests that ICMP error messages in response to -// UDP datagrams are processed correctly. RFC 1122 section 4.1.3.3 states that: -// "UDP MUST pass to the application layer all ICMP error messages that it -// receives from the IP layer." -// -// The test cases are parametrized in 3 dimensions: 1. the UDP socket is either -// put into connection mode or left connectionless, 2. the ICMP message type -// and code, and 3. the method by which the ICMP error is observed on the -// socket: sendto, recv, or getsockopt(SO_ERROR). -// -// Linux's udp(7) man page states: "All fatal errors will be passed to the user -// as an error return even when the socket is not connected. This includes -// asynchronous errors received from the network." In practice, the only -// combination of parameters to the test that causes an error to be observable -// on the UDP socket is receiving a port unreachable message on a connected -// socket. -func TestUDPICMPErrorPropagation(t *testing.T) { - for _, connect := range []connectionMode{true, false} { - for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} { - wantErrno := wantErrno(connect, icmpErr) - - for _, errDetect := range []errorDetection{ - {"SendTo", false, testSendTo}, - // Send to an address that's different from the one that caused an ICMP - // error to be returned. - {"SendToValid", true, testSendTo}, - {"Recv", false, testRecv}, - {"SockOpt", false, testSockOpt}, - } { - t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) { - dut := testbench.NewDUT(t) - - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, remoteFD) - - // Create a second, clean socket on the DUT to ensure that the ICMP - // error messages only affect the sockets they are intended for. - cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, cleanFD) - - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - if connect { - dut.Connect(t, remoteFD, conn.LocalAddr(t)) - dut.Connect(t, cleanFD, conn.LocalAddr(t)) - } - - dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t)) - udp, err := conn.Expect(t, testbench.UDP{}, time.Second) - if err != nil { - t.Fatalf("did not receive message from DUT: %s", err) - } - - sendICMPError(t, &conn, icmpErr, udp) - - errDetectConn := &conn - if errDetect.useValidConn { - // connClean is a UDP socket on the test runner that was not - // involved in the generation of the ICMP error. As such, - // interactions between it and the the DUT should be independent of - // the ICMP error at least at the port level. - connClean := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer connClean.Close(t) - - errDetectConn = &connClean - } - - errDetect.f(context.Background(), t, testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno}) - }) - } - } - } -} - -// TestICMPErrorDuringUDPRecv tests behavior when a UDP socket is in the middle -// of a blocking recv and receives an ICMP error. -func TestICMPErrorDuringUDPRecv(t *testing.T) { - for _, connect := range []connectionMode{true, false} { - for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} { - wantErrno := wantErrno(connect, icmpErr) - - t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) { - dut := testbench.NewDUT(t) - - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, remoteFD) - - // Create a second, clean socket on the DUT to ensure that the ICMP - // error messages only affect the sockets they are intended for. - cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, cleanFD) - - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - if connect { - dut.Connect(t, remoteFD, conn.LocalAddr(t)) - dut.Connect(t, cleanFD, conn.LocalAddr(t)) - } - - dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t)) - udp, err := conn.Expect(t, testbench.UDP{}, time.Second) - if err != nil { - t.Fatalf("did not receive message from DUT: %s", err) - } - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - - if wantErrno != unix.Errno(0) { - ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if ret != -1 { - t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno) - return - } - if err != wantErrno { - t.Errorf("recv during ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, wantErrno) - return - } - } - - if ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0); ret == -1 { - t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err) - } - }() - - go func() { - defer wg.Done() - - if ret, _, err := dut.RecvWithErrno(context.Background(), t, cleanFD, 100, 0); ret == -1 { - t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err) - } - }() - - // TODO(b/155684889) This sleep is to allow time for the DUT to - // actually call recv since we want the ICMP error to arrive during the - // blocking recv, and should be replaced when a better synchronization - // alternative is available. - time.Sleep(2 * time.Second) - - sendICMPError(t, &conn, icmpErr, udp) - - conn.Send(t, testbench.UDP{DstPort: &cleanPort}) - conn.Send(t, testbench.UDP{}) - wg.Wait() - }) - } - } -} |