summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/tcp/tcp_proxy.go3
-rw-r--r--test/packetimpact/runner/defs.bzl3
-rw-r--r--test/packetimpact/testbench/connections.go2
-rw-r--r--test/packetimpact/testbench/layers.go10
-rw-r--r--test/packetimpact/tests/BUILD14
-rw-r--r--test/packetimpact/tests/ipv6_fragment_icmp_error_test.go360
-rw-r--r--test/root/crictl_test.go2
-rw-r--r--test/syscalls/linux/BUILD35
-rw-r--r--test/syscalls/linux/partial_bad_buffer.cc5
-rw-r--r--test/syscalls/linux/sendfile.cc18
-rw-r--r--test/syscalls/linux/socket_generic.cc34
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc37
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc66
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h30
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc39
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc32
-rw-r--r--test/syscalls/linux/tcp_socket.cc166
-rw-r--r--test/syscalls/linux/udp_socket.cc36
18 files changed, 653 insertions, 239 deletions
diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go
index 5afe10f69..9fe60080c 100644
--- a/test/benchmarks/tcp/tcp_proxy.go
+++ b/test/benchmarks/tcp/tcp_proxy.go
@@ -208,9 +208,6 @@ func newNetstackImpl(mode string) (impl, error) {
if err := s.CreateNIC(nicID, fifo.New(ep, runtime.GOMAXPROCS(0), 1000)); err != nil {
return nil, fmt.Errorf("error creating NIC %q: %v", *iface, err)
}
- if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- return nil, fmt.Errorf("error adding ARP address to %q: %v", *iface, err)
- }
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, parsedAddr); err != nil {
return nil, fmt.Errorf("error adding IP address to %q: %v", *iface, err)
}
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index c03c2c62c..1038e3c8d 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -258,6 +258,9 @@ ALL_TESTS = [
name = "ipv6_fragment_reassembly",
),
PacketimpactTestInfo(
+ name = "ipv6_fragment_icmp_error",
+ ),
+ PacketimpactTestInfo(
name = "udp_send_recv_dgram",
),
PacketimpactTestInfo(
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 030a73c3c..919b4fd25 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -72,7 +72,7 @@ func pickPort(domain, typ int) (fd int, port uint16, err error) {
}
sa, err = unix.Getsockname(fd)
if err != nil {
- return -1, 0, fmt.Errorf("fail in Getsocketname(%d): %w", fd, err)
+ return -1, 0, fmt.Errorf("unix.Getsocketname(%d): %w", fd, err)
}
port, err = portFromSockaddr(sa)
if err != nil {
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index 2fb7ca9ba..7401a1991 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -298,7 +298,7 @@ func (l *IPv4) ToBytes() ([]byte, error) {
// An IPv4 header is variable length depending on the size of the Options.
hdrLen := header.IPv4MinimumSize
if l.Options != nil {
- hdrLen += l.Options.AllocationSize()
+ hdrLen += l.Options.SizeWithPadding()
if hdrLen > header.IPv4MaximumHeaderSize {
// While ToBytes can be called on packets that were received as well
// as packets locally generated, it is physically impossible for a
@@ -410,13 +410,7 @@ func Address(v tcpip.Address) *tcpip.Address {
// continues parsing further encapsulations.
func parseIPv4(b []byte) (Layer, layerParser) {
h := header.IPv4(b)
- hdrLen := h.HeaderLength()
- // Even if there are no options, we set an empty options field instead of nil
- // so that the decision to compare is up to the caller of that comparison.
- var options header.IPv4Options
- if hdrLen > header.IPv4MinimumSize {
- options = append(options, h.Options()...)
- }
+ options := h.Options()
tos, _ := h.TOS()
ipv4 := IPv4{
IHL: Uint8(h.HeaderLength()),
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index c30c77a17..33bd070c1 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -323,6 +323,20 @@ packetimpact_testbench(
)
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 = "udp_send_recv_dgram",
srcs = ["udp_send_recv_dgram_test.go"],
deps = [
diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
new file mode 100644
index 000000000..e058fb0d8
--- /dev/null
+++ b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
@@ -0,0 +1,360 @@
+// 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"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+ "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.RegisterFlags(flag.CommandLine)
+}
+
+func fragmentedICMPEchoRequest(t *testing.T, conn *testbench.Connection, 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(
+ icmpv6Header,
+ tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()),
+ tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()),
+ buffer.NewVectorisedView(len(payload), []buffer.View{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) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, _ := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotEchoReply, err := ipv6Conn.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) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotErrorMessage, err := ipv6Conn.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) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotErrorMessage, err := ipv6Conn.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/root/crictl_test.go b/test/root/crictl_test.go
index 11ac5cb52..735dff107 100644
--- a/test/root/crictl_test.go
+++ b/test/root/crictl_test.go
@@ -480,7 +480,7 @@ func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) {
}
// Wait for containerd to boot.
- if err := testutil.WaitUntilRead(startupR, "Start streaming server", nil, 10*time.Second); err != nil {
+ if err := testutil.WaitUntilRead(startupR, "Start streaming server", 10*time.Second); err != nil {
t.Fatalf("failed to start containerd: %v", err)
}
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 2350f7e69..50baafbf7 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -19,7 +19,6 @@ exports_files(
"socket_ip_udp_loopback_blocking.cc",
"socket_ip_udp_loopback_nonblock.cc",
"socket_ip_unbound.cc",
- "socket_ipv4_tcp_unbound_external_networking_test.cc",
"socket_ipv4_udp_unbound_external_networking_test.cc",
"socket_ipv4_udp_unbound_loopback.cc",
"socket_ipv4_udp_unbound_loopback_nogotsan.cc",
@@ -2505,24 +2504,6 @@ cc_library(
alwayslink = 1,
)
-cc_library(
- name = "socket_ipv4_tcp_unbound_external_networking_test_cases",
- testonly = 1,
- srcs = [
- "socket_ipv4_tcp_unbound_external_networking.cc",
- ],
- hdrs = [
- "socket_ipv4_tcp_unbound_external_networking.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- gtest,
- "//test/util:test_util",
- ],
- alwayslink = 1,
-)
-
cc_binary(
name = "socket_abstract_test",
testonly = 1,
@@ -2718,22 +2699,6 @@ cc_binary(
)
cc_binary(
- name = "socket_ipv4_tcp_unbound_external_networking_test",
- testonly = 1,
- srcs = [
- "socket_ipv4_tcp_unbound_external_networking_test.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ipv4_tcp_unbound_external_networking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
name = "socket_bind_to_device_test",
testonly = 1,
srcs = [
diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc
index df7129acc..13afa0eaf 100644
--- a/test/syscalls/linux/partial_bad_buffer.cc
+++ b/test/syscalls/linux/partial_bad_buffer.cc
@@ -320,7 +320,10 @@ PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
// EFAULT. It also verifies that passing a buffer which is made up of 2
// pages one valid and one guard page succeeds as long as the write is
// for exactly the size of 1 page.
-TEST_F(PartialBadBufferTest, SendMsgTCP) {
+TEST_F(PartialBadBufferTest, SendMsgTCP_NoRandomSave) {
+ // FIXME(b/171436815): Netstack save/restore is broken.
+ const DisableSave ds;
+
auto listen_socket =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc
index cf0977118..3924e0001 100644
--- a/test/syscalls/linux/sendfile.cc
+++ b/test/syscalls/linux/sendfile.cc
@@ -631,6 +631,24 @@ TEST(SendFileTest, SendFileToPipe) {
SyscallSucceedsWithValue(kDataSize));
}
+TEST(SendFileTest, SendFileToSelf_NoRandomSave) {
+ int rawfd;
+ ASSERT_THAT(rawfd = memfd_create("memfd", 0), SyscallSucceeds());
+ const FileDescriptor fd(rawfd);
+
+ char c = 0x01;
+ ASSERT_THAT(WriteFd(fd.get(), &c, 1), SyscallSucceedsWithValue(1));
+
+ // Arbitrarily chosen to make sendfile() take long enough that the sentry
+ // watchdog usually fires unless it's reset by sendfile() between iterations
+ // of the buffered copy. See b/172076632.
+ constexpr size_t kSendfileSize = 0xa00000;
+
+ off_t offset = 0;
+ ASSERT_THAT(sendfile(fd.get(), fd.get(), &offset, kSendfileSize),
+ SyscallSucceedsWithValue(kSendfileSize));
+}
+
static volatile int signaled = 0;
void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; }
diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc
index 5d39e6fbd..c81ba031d 100644
--- a/test/syscalls/linux/socket_generic.cc
+++ b/test/syscalls/linux/socket_generic.cc
@@ -818,5 +818,39 @@ TEST_P(AllSocketPairTest, GetSockoptProtocol) {
}
}
+TEST_P(AllSocketPairTest, SetAndGetBooleanSocketOptions) {
+ int sock_opts[] = {SO_BROADCAST, SO_PASSCRED};
+ for (int sock_opt : sock_opts) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+ int enable = -1;
+ socklen_t enableLen = sizeof(enable);
+
+ // Test that the option is initially set to false.
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ &enableLen),
+ SyscallSucceeds());
+ ASSERT_EQ(enableLen, sizeof(enable));
+ EXPECT_EQ(enable, 0) << absl::StrFormat(
+ "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d",
+ sock_opt, enable);
+
+ // Test that setting the option to true is reflected in the subsequent
+ // call to getsockopt(2).
+ enable = 1;
+ ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ sizeof(enable)),
+ SyscallSucceeds());
+ enable = -1;
+ enableLen = sizeof(enable);
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ &enableLen),
+ SyscallSucceeds());
+ ASSERT_EQ(enableLen, sizeof(enable));
+ EXPECT_EQ(enable, 1) << absl::StrFormat(
+ "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d",
+ sock_opt, enable);
+ }
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index e19a83413..27e9816eb 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -1185,19 +1185,44 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) {
listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen));
ASSERT_EQ(addrlen, listener.addr_len);
- // TODO(gvisor.dev/issue/3812): Remove after SO_ERROR is fixed.
- if (IsRunningOnGvisor()) {
- char buf[10];
- ASSERT_THAT(ReadFd(accept_fd.get(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ECONNRESET));
- } else {
+ // Wait for accept_fd to process the RST.
+ const int kTimeout = 10000;
+ struct pollfd pfd = {
+ .fd = accept_fd.get(),
+ .events = POLLIN,
+ };
+ ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+ ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR);
+
+ {
int err;
socklen_t optlen = sizeof(err);
ASSERT_THAT(
getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
SyscallSucceeds());
+ // This should return ECONNRESET as the socket just received a RST packet
+ // from the peer.
+ ASSERT_EQ(optlen, sizeof(err));
ASSERT_EQ(err, ECONNRESET);
+ }
+ {
+ int err;
+ socklen_t optlen = sizeof(err);
+ ASSERT_THAT(
+ getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
+ SyscallSucceeds());
+ // This should return no error as the previous getsockopt call would have
+ // cleared the socket error.
ASSERT_EQ(optlen, sizeof(err));
+ ASSERT_EQ(err, 0);
+ }
+ {
+ sockaddr_storage peer_addr;
+ socklen_t addrlen = sizeof(peer_addr);
+ // The socket is not connected anymore and should return ENOTCONN.
+ ASSERT_THAT(getpeername(accept_fd.get(),
+ reinterpret_cast<sockaddr*>(&peer_addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
}
}
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
deleted file mode 100644
index 80f12b0a9..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 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.
-
-#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <cstdio>
-#include <cstring>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Verifies that a newly instantiated TCP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, TCPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated TCP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, SetTCPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h
deleted file mode 100644
index fb582b224..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 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.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to unbound IPv4 TCP sockets in a sandbox
-// with external networking support.
-using IPv4TCPUnboundExternalNetworkingSocketTest = SimpleSocketTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc
deleted file mode 100644
index 797c4174e..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2019 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.
-
-#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-std::vector<SocketKind> GetSockets() {
- return ApplyVec<SocketKind>(
- IPv4TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(IPv4TCPUnboundSockets,
- IPv4TCPUnboundExternalNetworkingSocketTest,
- ::testing::ValuesIn(GetSockets()));
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
index b206137eb..2eecb0866 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
@@ -76,38 +76,6 @@ void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() {
found_net_interfaces_ = true;
}
-// Verifies that a newly instantiated UDP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated UDP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, SetUDPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
// Verifies that a broadcast UDP packet will arrive at all UDP sockets with
// the destination port number.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 0b25d8b5f..714848b8e 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -467,7 +467,7 @@ TEST_P(TcpSocketTest, PollWithFullBufferBlocks) {
TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) {
FillSocketBuffers(first_fd, second_fd);
- constexpr int timeout = 2;
+ constexpr int timeout = 10;
struct timeval tv = {.tv_sec = timeout, .tv_usec = 0};
EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
SyscallSucceeds());
@@ -485,7 +485,7 @@ TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) {
});
// Wait for the thread to be blocked on write.
- absl::SleepFor(absl::Milliseconds(50));
+ absl::SleepFor(absl::Milliseconds(250));
// Socket close does not have any effect on a blocked write.
ASSERT_THAT(close(first_fd), SyscallSucceeds());
// Indicate to the cleanup routine that we are already closed.
@@ -518,7 +518,7 @@ TEST_P(TcpSocketTest, ClosedReadBlockingSocket) {
});
// Wait for the thread to be blocked on read.
- absl::SleepFor(absl::Milliseconds(50));
+ absl::SleepFor(absl::Milliseconds(250));
// Socket close does not have any effect on a blocked read.
ASSERT_THAT(close(first_fd), SyscallSucceeds());
// Indicate to the cleanup routine that we are already closed.
@@ -964,37 +964,156 @@ TEST_P(TcpSocketTest, PollAfterShutdown) {
SyscallSucceedsWithValue(1));
}
-TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectRetry) {
+ const FileDescriptor listener =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
// Initialize address to the loopback one.
sockaddr_storage addr =
ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
socklen_t addrlen = sizeof(addr);
- const FileDescriptor s =
+ // Bind to some port but don't listen yet.
+ ASSERT_THAT(
+ bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Get the address we're bound to, then connect to it. We need to do this
+ // because we're allowing the stack to pick a port for us.
+ ASSERT_THAT(getsockname(listener.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
+
+ FileDescriptor connector =
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
+ // Verify that connect fails.
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNREFUSED));
- ASSERT_THAT(RetryEINTR(connect)(
+ // Now start listening
+ ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds());
+
+ // TODO(gvisor.dev/issue/3828): Issuing connect() again on a socket that
+ // failed first connect should succeed.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+ return;
+ }
+
+ // Verify that connect now succeeds.
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Accept the connection.
+ const FileDescriptor accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listener.get(), nullptr, nullptr));
+}
+
+// nonBlockingConnectNoListener returns a socket on which a connect that is
+// expected to fail has been issued.
+PosixErrorOr<FileDescriptor> nonBlockingConnectNoListener(const int family,
+ sockaddr_storage addr,
+ socklen_t addrlen) {
+ // We will first create a socket and bind to ensure we bind a port but will
+ // not call listen on this socket.
+ // Then we will create a new socket that will connect to the port bound by
+ // the first socket and that shoud fail.
+ constexpr int sock_type = SOCK_STREAM | SOCK_NONBLOCK;
+ int b_sock;
+ RETURN_ERROR_IF_SYSCALL_FAIL(b_sock = socket(family, sock_type, IPPROTO_TCP));
+ FileDescriptor b(b_sock);
+ EXPECT_THAT(bind(b.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Get the address bound by the listening socket.
+ EXPECT_THAT(
+ getsockname(b.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
+
+ // Now create another socket and issue a connect on this one. This connect
+ // should fail as there is no listener.
+ int c_sock;
+ RETURN_ERROR_IF_SYSCALL_FAIL(c_sock = socket(family, sock_type, IPPROTO_TCP));
+ FileDescriptor s(c_sock);
+
+ // Now connect to the bound address and this should fail as nothing
+ // is listening on the bound address.
+ EXPECT_THAT(RetryEINTR(connect)(
s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
SyscallFailsWithErrno(EINPROGRESS));
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
- SyscallSucceedsWithValue(1));
+ // Wait for the connect to fail.
+ struct pollfd poll_fd = {s.get(), POLLERR, 0};
+ EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1));
+ return std::move(s);
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
int err;
socklen_t optlen = sizeof(err);
ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
SyscallSucceeds());
-
+ ASSERT_THAT(optlen, sizeof(err));
EXPECT_EQ(err, ECONNREFUSED);
+
+ unsigned char c;
+ ASSERT_THAT(read(s.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0));
+ int opts;
+ EXPECT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
+ opts &= ~O_NONBLOCK;
+ EXPECT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
+ // Try connecting again.
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerRead) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
+
+ unsigned char c;
+ ASSERT_THAT(read(s.get(), &c, 1), SyscallFailsWithErrno(ECONNREFUSED));
+ ASSERT_THAT(read(s.get(), &c, 1), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerPeek) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
+
+ unsigned char c;
+ ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK),
+ SyscallFailsWithErrno(ECONNREFUSED));
+ ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
}
TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) {
@@ -1235,6 +1354,19 @@ TEST_P(SimpleTcpSocketTest, CleanupOnConnectionRefused) {
// Attempt #2, with the new socket and reused addr our connect should fail in
// the same way as before, not with an EADDRINUSE.
+ //
+ // TODO(gvisor.dev/issue/3828): 2nd connect on a socket which failed connect
+ // first time should succeed.
+ // gVisor never issues the second connect and returns ECONNABORTED instead.
+ // Linux actually sends a SYN again and gets a RST and correctly returns
+ // ECONNREFUSED.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(connect(client_s.get(),
+ reinterpret_cast<const struct sockaddr*>(&bound_addr),
+ bound_addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+ return;
+ }
ASSERT_THAT(connect(client_s.get(),
reinterpret_cast<const struct sockaddr*>(&bound_addr),
bound_addrlen),
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
index d65275fd3..34255bfb8 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -374,6 +374,42 @@ TEST_P(UdpSocketTest, BindInUse) {
SyscallFailsWithErrno(EADDRINUSE));
}
+TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ // Discover a free unused port by creating a new UDP socket, binding it
+ // recording the just bound port and closing it. This is not guaranteed as it
+ // can still race with other port UDP sockets trying to bind a port at the
+ // same time.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ socklen_t addrlen = sizeof(addr_storage);
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds());
+ ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds());
+ EXPECT_EQ(addrlen, addrlen_);
+ EXPECT_NE(*Port(&addr_storage), 0);
+ ASSERT_THAT(close(s.release()), SyscallSucceeds());
+
+ // Now connect to the port that we just released. This should generate an
+ // ECONNREFUSED error.
+ ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds());
+ char buf[512];
+ RandomizeBuffer(buf, sizeof(buf));
+ // Send from sock_ to an unbound port.
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ // Now verify that we got an ICMP error back of ECONNREFUSED.
+ int err;
+ socklen_t optlen = sizeof(err);
+ ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
+ SyscallSucceeds());
+ ASSERT_EQ(err, ECONNREFUSED);
+ ASSERT_EQ(optlen, sizeof(err));
+}
+
TEST_P(UdpSocketTest, ReceiveAfterConnect) {
ASSERT_NO_ERRNO(BindLoopback());
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());