diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/benchmarks/tcp/tcp_proxy.go | 3 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 3 | ||||
-rw-r--r-- | test/packetimpact/testbench/connections.go | 2 | ||||
-rw-r--r-- | test/packetimpact/testbench/layers.go | 10 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 14 | ||||
-rw-r--r-- | test/packetimpact/tests/ipv6_fragment_icmp_error_test.go | 360 | ||||
-rw-r--r-- | test/root/crictl_test.go | 2 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 35 | ||||
-rw-r--r-- | test/syscalls/linux/partial_bad_buffer.cc | 5 | ||||
-rw-r--r-- | test/syscalls/linux/sendfile.cc | 18 | ||||
-rw-r--r-- | test/syscalls/linux/socket_generic.cc | 34 | ||||
-rw-r--r-- | test/syscalls/linux/socket_inet_loopback.cc | 37 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc | 66 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h | 30 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc | 39 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc | 32 | ||||
-rw-r--r-- | test/syscalls/linux/tcp_socket.cc | 166 | ||||
-rw-r--r-- | test/syscalls/linux/udp_socket.cc | 36 |
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()); |