// Copyright 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" #include "test/util/save_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" namespace gvisor { namespace testing { namespace { using ::testing::Gt; PosixErrorOr AddrPort(int family, sockaddr_storage const& addr) { switch (family) { case AF_INET: return static_cast( reinterpret_cast(&addr)->sin_port); case AF_INET6: return static_cast( reinterpret_cast(&addr)->sin6_port); default: return PosixError(EINVAL, absl::StrCat("unknown socket family: ", family)); } } PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { switch (family) { case AF_INET: reinterpret_cast(addr)->sin_port = port; return NoError(); case AF_INET6: reinterpret_cast(addr)->sin6_port = port; return NoError(); default: return PosixError(EINVAL, absl::StrCat("unknown socket family: ", family)); } } struct TestParam { TestAddress listener; TestAddress connector; }; std::string DescribeTestParam(::testing::TestParamInfo const& info) { return absl::StrCat("Listen", info.param.listener.description, "_Connect", info.param.connector.description); } using SocketInetLoopbackTest = ::testing::TestWithParam; TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) { int fd[2] = {}; // Valid AF but invalid for socketpair(2) return ESOCKTNOSUPPORT. ASSERT_THAT(socketpair(AF_INET, 0, 0, fd), SyscallFailsWithErrno(ESOCKTNOSUPPORT)); ASSERT_THAT(socketpair(AF_INET6, 0, 0, fd), SyscallFailsWithErrno(ESOCKTNOSUPPORT)); // Invalid AF will return ENOAFSUPPORT. ASSERT_THAT(socketpair(AF_MAX, 0, 0, fd), SyscallFailsWithErrno(EAFNOSUPPORT)); ASSERT_THAT(socketpair(8675309, 0, 0, fd), SyscallFailsWithErrno(EAFNOSUPPORT)); } enum class Operation { Bind, Connect, SendTo, }; std::string OperationToString(Operation operation) { switch (operation) { case Operation::Bind: return "Bind"; case Operation::Connect: return "Connect"; case Operation::SendTo: return "SendTo"; } } using OperationSequence = std::vector; using DualStackSocketTest = ::testing::TestWithParam>; TEST_P(DualStackSocketTest, AddressOperations) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_DGRAM, 0)); const TestAddress& addr = std::get<0>(GetParam()); const OperationSequence& operations = std::get<1>(GetParam()); auto addr_in = reinterpret_cast(&addr.addr); // sockets may only be bound once. Both `connect` and `sendto` cause a socket // to be bound. bool bound = false; for (const Operation& operation : operations) { bool sockname = false; bool peername = false; switch (operation) { case Operation::Bind: { ASSERT_NO_ERRNO(SetAddrPort( addr.family(), const_cast(&addr.addr), 0)); int bind_ret = bind(fd.get(), addr_in, addr.addr_len); // Dual stack sockets may only be bound to AF_INET6. if (!bound && addr.family() == AF_INET6) { EXPECT_THAT(bind_ret, SyscallSucceeds()); bound = true; sockname = true; } else { EXPECT_THAT(bind_ret, SyscallFailsWithErrno(EINVAL)); } break; } case Operation::Connect: { ASSERT_NO_ERRNO(SetAddrPort( addr.family(), const_cast(&addr.addr), 1337)); EXPECT_THAT(connect(fd.get(), addr_in, addr.addr_len), SyscallSucceeds()) << GetAddrStr(addr_in); bound = true; sockname = true; peername = true; break; } case Operation::SendTo: { const char payload[] = "hello"; ASSERT_NO_ERRNO(SetAddrPort( addr.family(), const_cast(&addr.addr), 1337)); ssize_t sendto_ret = sendto(fd.get(), &payload, sizeof(payload), 0, addr_in, addr.addr_len); EXPECT_THAT(sendto_ret, SyscallSucceedsWithValue(sizeof(payload))); sockname = !bound; bound = true; break; } } if (sockname) { sockaddr_storage sock_addr; socklen_t addrlen = sizeof(sock_addr); ASSERT_THAT(getsockname(fd.get(), reinterpret_cast(&sock_addr), &addrlen), SyscallSucceeds()); ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6)); auto sock_addr_in6 = reinterpret_cast(&sock_addr); if (operation == Operation::SendTo) { EXPECT_EQ(sock_addr_in6->sin6_family, AF_INET6); EXPECT_TRUE(IN6_IS_ADDR_UNSPECIFIED(sock_addr_in6->sin6_addr.s6_addr32)) << OperationToString(operation) << " getsocknam=" << GetAddrStr(reinterpret_cast(&sock_addr)); EXPECT_NE(sock_addr_in6->sin6_port, 0); } else if (IN6_IS_ADDR_V4MAPPED( reinterpret_cast(addr_in) ->sin6_addr.s6_addr32)) { EXPECT_TRUE(IN6_IS_ADDR_V4MAPPED(sock_addr_in6->sin6_addr.s6_addr32)) << OperationToString(operation) << " getsocknam=" << GetAddrStr(reinterpret_cast(&sock_addr)); } } if (peername) { sockaddr_storage peer_addr; socklen_t addrlen = sizeof(peer_addr); ASSERT_THAT(getpeername(fd.get(), reinterpret_cast(&peer_addr), &addrlen), SyscallSucceeds()); ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6)); if (addr.family() == AF_INET || IN6_IS_ADDR_V4MAPPED(reinterpret_cast(addr_in) ->sin6_addr.s6_addr32)) { EXPECT_TRUE(IN6_IS_ADDR_V4MAPPED( reinterpret_cast(&peer_addr) ->sin6_addr.s6_addr32)) << OperationToString(operation) << " getpeername=" << GetAddrStr(reinterpret_cast(&peer_addr)); } } } } // TODO(gvisor.dev/issues/1556): uncomment V4MappedAny. INSTANTIATE_TEST_SUITE_P( All, DualStackSocketTest, ::testing::Combine( ::testing::Values(V4Any(), V4Loopback(), /*V4MappedAny(),*/ V4MappedLoopback(), V6Any(), V6Loopback()), ::testing::ValuesIn( {{Operation::Bind, Operation::Connect, Operation::SendTo}, {Operation::Bind, Operation::SendTo, Operation::Connect}, {Operation::Connect, Operation::Bind, Operation::SendTo}, {Operation::Connect, Operation::SendTo, Operation::Bind}, {Operation::SendTo, Operation::Bind, Operation::Connect}, {Operation::SendTo, Operation::Connect, Operation::Bind}})), [](::testing::TestParamInfo< std::tuple> const& info) { const TestAddress& addr = std::get<0>(info.param); const OperationSequence& operations = std::get<1>(info.param); std::string s = addr.description; for (const Operation& operation : operations) { absl::StrAppend(&s, OperationToString(operation)); } return s; }); void tcpSimpleConnectTest(TestAddress const& listener, TestAddress const& connector, bool unbound) { // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; if (!unbound) { ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); } ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Connect to the listening socket. const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. // // We have to assign a name to the accepted socket, as unamed temporary // objects are destructed upon full evaluation of the expression it is in, // potentially causing the connecting socket to fail to shutdown properly. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); ASSERT_THAT(shutdown(listen_fd.get(), SHUT_RDWR), SyscallSucceeds()); ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds()); } TEST_P(SocketInetLoopbackTest, TCP) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; tcpSimpleConnectTest(listener, connector, true); } TEST_P(SocketInetLoopbackTest, TCPListenUnbound) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; tcpSimpleConnectTest(listener, connector, false); } TEST_P(SocketInetLoopbackTest, TCPListenClose) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), 1001), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); DisableSave ds; // Too many system calls. sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); constexpr int kFDs = 2048; constexpr int kThreadCount = 4; constexpr int kFDsPerThread = kFDs / kThreadCount; FileDescriptor clients[kFDs]; std::unique_ptr threads[kThreadCount]; for (int i = 0; i < kFDs; i++) { clients[i] = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); } for (int i = 0; i < kThreadCount; i++) { threads[i] = absl::make_unique([&connector, &conn_addr, &clients, i]() { for (int j = 0; j < kFDsPerThread; j++) { int k = i * kFDsPerThread + j; int ret = connect(clients[k].get(), reinterpret_cast(&conn_addr), connector.addr_len); if (ret != 0) { EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); } } }); } for (int i = 0; i < kThreadCount; i++) { threads[i]->Join(); } for (int i = 0; i < 32; i++) { auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); } // TODO(b/138400178): Fix cooperative S/R failure when ds.reset() is invoked // before function end. // ds.reset(); } TEST_P(SocketInetLoopbackTest, TCPbacklog) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), 2), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); int i = 0; while (1) { int ret; // Connect to the listening socket. const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ret = connect(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len); if (ret != 0) { EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); struct pollfd pfd = { .fd = conn_fd.get(), .events = POLLOUT, }; ret = poll(&pfd, 1, 3000); if (ret == 0) break; EXPECT_THAT(ret, SyscallSucceedsWithValue(1)); } EXPECT_THAT(RetryEINTR(send)(conn_fd.get(), &i, sizeof(i), 0), SyscallSucceedsWithValue(sizeof(i))); ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds()); i++; } for (; i != 0; i--) { // Accept the connection. // // We have to assign a name to the accepted socket, as unamed temporary // objects are destructed upon full evaluation of the expression it is in, // potentially causing the connecting socket to fail to shutdown properly. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); } } // TCPFinWait2Test creates a pair of connected sockets then closes one end to // trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local // IP/port on a new socket and tries to connect. The connect should fail w/ // an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the // connect again with a new socket and this time it should succeed. // // TCP timers are not S/R today, this can cause this test to be flaky when run // under random S/R due to timer being reset on a restore. TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); // Lower FIN_WAIT2 state to 5 seconds for test. constexpr int kTCPLingerTimeout = 5; EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), SyscallSucceedsWithValue(0)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // Get the address/port bound by the connecting socket. sockaddr_storage conn_bound_addr; socklen_t conn_addrlen = connector.addr_len; ASSERT_THAT( getsockname(conn_fd.get(), reinterpret_cast(&conn_bound_addr), &conn_addrlen), SyscallSucceeds()); // close the connecting FD to trigger FIN_WAIT2 on the connected fd. conn_fd.reset(); // Now bind and connect a new socket. const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); // Disable cooperative saves after this point. As a save between the first // bind/connect and the second one can cause the linger timeout timer to // be restarted causing the final bind/connect to fail. DisableSave ds; // TODO(gvisor.dev/issue/1030): Portmanager does not track all 5 tuple // reservations which causes the bind() to succeed on gVisor but connect // correctly fails. if (IsRunningOnGvisor()) { ASSERT_THAT( bind(conn_fd2.get(), reinterpret_cast(&conn_bound_addr), conn_addrlen), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), reinterpret_cast(&conn_addr), conn_addrlen), SyscallFailsWithErrno(EADDRINUSE)); } else { ASSERT_THAT( bind(conn_fd2.get(), reinterpret_cast(&conn_bound_addr), conn_addrlen), SyscallFailsWithErrno(EADDRINUSE)); } // Sleep for a little over the linger timeout to reduce flakiness in // save/restore tests. absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2)); ds.reset(); if (!IsRunningOnGvisor()) { ASSERT_THAT( bind(conn_fd2.get(), reinterpret_cast(&conn_bound_addr), conn_addrlen), SyscallSucceeds()); } ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), reinterpret_cast(&conn_addr), conn_addrlen), SyscallSucceeds()); } // TCPLinger2TimeoutAfterClose creates a pair of connected sockets // then closes one end to trigger FIN_WAIT2 state for the closed endpont. // It then sleeps for the TCP_LINGER2 timeout and verifies that bind/ // connecting the same address succeeds. // // TCP timers are not S/R today, this can cause this test to be flaky when run // under random S/R due to timer being reset on a restore. TEST_P(SocketInetLoopbackTest, TCPLinger2TimeoutAfterClose_NoRandomSave) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // Get the address/port bound by the connecting socket. sockaddr_storage conn_bound_addr; socklen_t conn_addrlen = connector.addr_len; ASSERT_THAT( getsockname(conn_fd.get(), reinterpret_cast(&conn_bound_addr), &conn_addrlen), SyscallSucceeds()); constexpr int kTCPLingerTimeout = 5; EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), SyscallSucceedsWithValue(0)); // close the connecting FD to trigger FIN_WAIT2 on the connected fd. conn_fd.reset(); absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1)); // Now bind and connect a new socket and verify that we can immediately // rebind the address bound by the conn_fd as it never entered TIME_WAIT. const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); ASSERT_THAT(bind(conn_fd2.get(), reinterpret_cast(&conn_bound_addr), conn_addrlen), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), reinterpret_cast(&conn_addr), conn_addrlen), SyscallSucceeds()); } // TCPResetAfterClose creates a pair of connected sockets then closes // one end to trigger FIN_WAIT2 state for the closed endpoint verifies // that we generate RSTs for any new data after the socket is fully // closed. TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // close the connecting FD to trigger FIN_WAIT2 on the connected fd. conn_fd.reset(); int data = 1234; // Now send data which should trigger a RST as the other end should // have timed out and closed the socket. EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0), SyscallSucceeds()); // Sleep for a shortwhile to get a RST back. absl::SleepFor(absl::Seconds(1)); // Try writing again and we should get an EPIPE back. EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0), SyscallFailsWithErrno(EPIPE)); // Trying to read should return zero as the other end did send // us a FIN. We do it twice to verify that the RST does not cause an // ECONNRESET on the read after EOF has been read by applicaiton. EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0), SyscallSucceedsWithValue(0)); EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0), SyscallSucceedsWithValue(0)); } // This test is disabled under random save as the the restore run // results in the stack.Seed() being different which can cause // sequence number of final connect to be one that is considered // old and can cause the test to be flaky. TEST_P(SocketInetLoopbackTest, TCPTimeWaitTest_NoRandomSave) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); // We disable saves after this point as a S/R causes the netstack seed // to be regenerated which changes what ports/ISN is picked for a given // tuple (src ip,src port, dst ip, dst port). This can cause the final // SYN to use a sequence number that looks like one from the current // connection in TIME_WAIT and will not be accepted causing the test // to timeout. // // TODO(gvisor.dev/issue/940): S/R portSeed/portHint DisableSave ds; sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // Get the address/port bound by the connecting socket. sockaddr_storage conn_bound_addr; socklen_t conn_addrlen = connector.addr_len; ASSERT_THAT( getsockname(conn_fd.get(), reinterpret_cast(&conn_bound_addr), &conn_addrlen), SyscallSucceeds()); // close the accept FD to trigger TIME_WAIT on the accepted socket which // should cause the conn_fd to follow CLOSE_WAIT->LAST_ACK->CLOSED instead of // TIME_WAIT. accepted.reset(); absl::SleepFor(absl::Seconds(1)); conn_fd.reset(); absl::SleepFor(absl::Seconds(1)); // Now bind and connect a new socket and verify that we can immediately // rebind the address bound by the conn_fd as it never entered TIME_WAIT. const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); ASSERT_THAT(bind(conn_fd2.get(), reinterpret_cast(&conn_bound_addr), conn_addrlen), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), reinterpret_cast(&conn_addr), conn_addrlen), SyscallSucceeds()); } TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); const uint16_t port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Set the userTimeout on the listening socket. constexpr int kUserTimeout = 10; ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, &kUserTimeout, sizeof(kUserTimeout)), SyscallSucceeds()); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // Verify that the accepted socket inherited the user timeout set on // listening socket. int get = -1; socklen_t get_len = sizeof(get); ASSERT_THAT( getsockopt(accepted.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, &get, &get_len), SyscallSucceeds()); EXPECT_EQ(get_len, sizeof(get)); EXPECT_EQ(get, kUserTimeout); } // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not // saved. Enable S/R once issue is fixed. TEST_P(SocketInetLoopbackTest, TCPDeferAccept_NoRandomSave) { // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not // saved. Enable S/R issue is fixed. DisableSave ds; auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); const uint16_t port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Set the TCP_DEFER_ACCEPT on the listening socket. constexpr int kTCPDeferAccept = 3; ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &kTCPDeferAccept, sizeof(kTCPDeferAccept)), SyscallSucceeds()); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Set the listening socket to nonblock so that we can verify that there is no // connection in queue despite the connect above succeeding since the peer has // sent no data and TCP_DEFER_ACCEPT is set on the listening socket. Set the // FD to O_NONBLOCK. int opts; ASSERT_THAT(opts = fcntl(listen_fd.get(), F_GETFL), SyscallSucceeds()); opts |= O_NONBLOCK; ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), SyscallFailsWithErrno(EWOULDBLOCK)); // Set FD back to blocking. opts &= ~O_NONBLOCK; ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); // Now write some data to the socket. int data = 0; ASSERT_THAT(RetryEINTR(write)(conn_fd.get(), &data, sizeof(data)), SyscallSucceedsWithValue(sizeof(data))); // This should now cause the connection to complete and be delivered to the // accept socket. // Accept the connection. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); // Verify that the accepted socket returns the data written. int get = -1; ASSERT_THAT(RetryEINTR(recv)(accepted.get(), &get, sizeof(get), 0), SyscallSucceedsWithValue(sizeof(get))); EXPECT_EQ(get, data); } // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not // saved. Enable S/R once issue is fixed. TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout_NoRandomSave) { // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not // saved. Enable S/R once issue is fixed. DisableSave ds; auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; // Create the listening socket. const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage listen_addr = listener.addr; ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT(getsockname(listen_fd.get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); const uint16_t port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); // Set the TCP_DEFER_ACCEPT on the listening socket. constexpr int kTCPDeferAccept = 3; ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &kTCPDeferAccept, sizeof(kTCPDeferAccept)), SyscallSucceeds()); // Connect to the listening socket. FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); sockaddr_storage conn_addr = connector.addr; ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Set the listening socket to nonblock so that we can verify that there is no // connection in queue despite the connect above succeeding since the peer has // sent no data and TCP_DEFER_ACCEPT is set on the listening socket. Set the // FD to O_NONBLOCK. int opts; ASSERT_THAT(opts = fcntl(listen_fd.get(), F_GETFL), SyscallSucceeds()); opts |= O_NONBLOCK; ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); // Verify that there is no acceptable connection before TCP_DEFER_ACCEPT // timeout is hit. absl::SleepFor(absl::Seconds(kTCPDeferAccept - 1)); ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), SyscallFailsWithErrno(EWOULDBLOCK)); // Set FD back to blocking. opts &= ~O_NONBLOCK; ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); // Now sleep for a little over the TCP_DEFER_ACCEPT duration. When the timeout // is hit a SYN-ACK should be retransmitted by the listener as a last ditch // attempt to complete the connection with or without data. absl::SleepFor(absl::Seconds(2)); // Verify that we have a connection that can be accepted even though no // data was written. auto accepted = ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); } INSTANTIATE_TEST_SUITE_P( All, SocketInetLoopbackTest, ::testing::Values( // Listeners bound to IPv4 addresses refuse connections using IPv6 // addresses. TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()}, TestParam{V4Any(), V4MappedAny()}, TestParam{V4Any(), V4MappedLoopback()}, TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()}, TestParam{V4Loopback(), V4MappedLoopback()}, TestParam{V4MappedAny(), V4Any()}, TestParam{V4MappedAny(), V4Loopback()}, TestParam{V4MappedAny(), V4MappedAny()}, TestParam{V4MappedAny(), V4MappedLoopback()}, TestParam{V4MappedLoopback(), V4Any()}, TestParam{V4MappedLoopback(), V4Loopback()}, TestParam{V4MappedLoopback(), V4MappedLoopback()}, // Listeners bound to IN6ADDR_ANY accept all connections. TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V4MappedAny()}, TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()}, TestParam{V6Any(), V6Loopback()}, // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 // addresses. TestParam{V6Loopback(), V6Any()}, TestParam{V6Loopback(), V6Loopback()}), DescribeTestParam); using SocketInetReusePortTest = ::testing::TestWithParam; // TODO(gvisor.dev/issue/940): Remove _NoRandomSave when portHint/stack.Seed is // saved/restored. TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread_NoRandomSave) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; sockaddr_storage listen_addr = listener.addr; sockaddr_storage conn_addr = connector.addr; constexpr int kThreadCount = 3; constexpr int kConnectAttempts = 10000; // Create the listening socket. FileDescriptor listener_fds[kThreadCount]; for (int i = 0; i < kThreadCount; i++) { listener_fds[i] = ASSERT_NO_ERRNO_AND_VALUE( Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); int fd = listener_fds[i].get(); ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT( bind(fd, reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); ASSERT_THAT(listen(fd, 40), SyscallSucceeds()); // On the first bind we need to determine which port was bound. if (i != 0) { continue; } // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT( getsockname(listener_fds[0].get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); } std::atomic connects_received = ATOMIC_VAR_INIT(0); std::unique_ptr listen_thread[kThreadCount]; int accept_counts[kThreadCount] = {}; // TODO(avagin): figure how to not disable S/R for the whole test. // We need to take into account that this test executes a lot of system // calls from many threads. DisableSave ds; for (int i = 0; i < kThreadCount; i++) { listen_thread[i] = absl::make_unique( [&listener_fds, &accept_counts, i, &connects_received]() { do { auto fd = Accept(listener_fds[i].get(), nullptr, nullptr); if (!fd.ok()) { if (connects_received >= kConnectAttempts) { // Another thread have shutdown our read side causing the // accept to fail. break; } ASSERT_NO_ERRNO(fd); break; } // Receive some data from a socket to be sure that the connect() // system call has been completed on another side. // Do a short read and then close the socket to trigger a RST. This // ensures that both ends of the connection are cleaned up and no // goroutines hang around in TIME-WAIT. We do this so that this test // does not timeout under gotsan runs where lots of goroutines can // cause the test to use absurd amounts of memory. // // See: https://tools.ietf.org/html/rfc2525#page-50 section 2.17 uint16_t data; EXPECT_THAT( RetryEINTR(recv)(fd.ValueOrDie().get(), &data, sizeof(data), 0), SyscallSucceedsWithValue(sizeof(data))); accept_counts[i]++; } while (++connects_received < kConnectAttempts); // Shutdown all sockets to wake up other threads. for (int j = 0; j < kThreadCount; j++) { shutdown(listener_fds[j].get(), SHUT_RDWR); } }); } ScopedThread connecting_thread([&connector, &conn_addr]() { for (int i = 0; i < kConnectAttempts; i++) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); ASSERT_THAT( RetryEINTR(connect)(fd.get(), reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceeds()); // Do two separate sends to ensure two segments are received. This is // required for netstack where read is incorrectly assuming a whole // segment is read when endpoint.Read() is called which is technically // incorrect as the syscall that invoked endpoint.Read() may only // consume it partially. This results in a case where a close() of // such a socket does not trigger a RST in netstack due to the // endpoint assuming that the endpoint has no unread data. EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), SyscallSucceedsWithValue(sizeof(i))); // TODO(gvisor.dev/issue/1449): Remove this block once netstack correctly // generates a RST. if (IsRunningOnGvisor()) { EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), SyscallSucceedsWithValue(sizeof(i))); } } }); // Join threads to be sure that all connections have been counted connecting_thread.Join(); for (int i = 0; i < kThreadCount; i++) { listen_thread[i]->Join(); } // Check that connections are distributed fairly between listening sockets for (int i = 0; i < kThreadCount; i++) EXPECT_THAT(accept_counts[i], EquivalentWithin((kConnectAttempts / kThreadCount), 0.10)); } TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; sockaddr_storage listen_addr = listener.addr; sockaddr_storage conn_addr = connector.addr; constexpr int kThreadCount = 3; // Create the listening socket. FileDescriptor listener_fds[kThreadCount]; for (int i = 0; i < kThreadCount; i++) { listener_fds[i] = ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0)); int fd = listener_fds[i].get(); ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT( bind(fd, reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); // On the first bind we need to determine which port was bound. if (i != 0) { continue; } // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT( getsockname(listener_fds[0].get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); } constexpr int kConnectAttempts = 10000; std::atomic packets_received = ATOMIC_VAR_INIT(0); std::unique_ptr receiver_thread[kThreadCount]; int packets_per_socket[kThreadCount] = {}; // TODO(avagin): figure how to not disable S/R for the whole test. DisableSave ds; // Too expensive. for (int i = 0; i < kThreadCount; i++) { receiver_thread[i] = absl::make_unique( [&listener_fds, &packets_per_socket, i, &packets_received]() { do { struct sockaddr_storage addr = {}; socklen_t addrlen = sizeof(addr); int data; auto ret = RetryEINTR(recvfrom)( listener_fds[i].get(), &data, sizeof(data), 0, reinterpret_cast(&addr), &addrlen); if (packets_received < kConnectAttempts) { ASSERT_THAT(ret, SyscallSucceedsWithValue(sizeof(data))); } if (ret != sizeof(data)) { // Another thread may have shutdown our read side causing the // recvfrom to fail. break; } packets_received++; packets_per_socket[i]++; // A response is required to synchronize with the main thread, // otherwise the main thread can send more than can fit into receive // queues. EXPECT_THAT(RetryEINTR(sendto)( listener_fds[i].get(), &data, sizeof(data), 0, reinterpret_cast(&addr), addrlen), SyscallSucceedsWithValue(sizeof(data))); } while (packets_received < kConnectAttempts); // Shutdown all sockets to wake up other threads. for (int j = 0; j < kThreadCount; j++) shutdown(listener_fds[j].get(), SHUT_RDWR); }); } ScopedThread main_thread([&connector, &conn_addr]() { for (int i = 0; i < kConnectAttempts; i++) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0)); EXPECT_THAT(RetryEINTR(sendto)(fd.get(), &i, sizeof(i), 0, reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceedsWithValue(sizeof(i))); int data; EXPECT_THAT(RetryEINTR(recv)(fd.get(), &data, sizeof(data), 0), SyscallSucceedsWithValue(sizeof(data))); } }); main_thread.Join(); // Join threads to be sure that all connections have been counted for (int i = 0; i < kThreadCount; i++) { receiver_thread[i]->Join(); } // Check that packets are distributed fairly between listening sockets. for (int i = 0; i < kThreadCount; i++) EXPECT_THAT(packets_per_socket[i], EquivalentWithin((kConnectAttempts / kThreadCount), 0.10)); } TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort) { auto const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; sockaddr_storage listen_addr = listener.addr; sockaddr_storage conn_addr = connector.addr; constexpr int kThreadCount = 3; // TODO(b/141211329): endpointsByNic.seed has to be saved/restored. const DisableSave ds141211329; // Create listening sockets. FileDescriptor listener_fds[kThreadCount]; for (int i = 0; i < kThreadCount; i++) { listener_fds[i] = ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0)); int fd = listener_fds[i].get(); ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT( bind(fd, reinterpret_cast(&listen_addr), listener.addr_len), SyscallSucceeds()); // On the first bind we need to determine which port was bound. if (i != 0) { continue; } // Get the port bound by the listening socket. socklen_t addrlen = listener.addr_len; ASSERT_THAT( getsockname(listener_fds[0].get(), reinterpret_cast(&listen_addr), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); } constexpr int kConnectAttempts = 10; FileDescriptor client_fds[kConnectAttempts]; // Do the first run without save/restore. DisableSave ds; for (int i = 0; i < kConnectAttempts; i++) { client_fds[i] = ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0)); EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0, reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceedsWithValue(sizeof(i))); } ds.reset(); // Check that a mapping of client and server sockets has // not been change after save/restore. for (int i = 0; i < kConnectAttempts; i++) { EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0, reinterpret_cast(&conn_addr), connector.addr_len), SyscallSucceedsWithValue(sizeof(i))); } struct pollfd pollfds[kThreadCount]; for (int i = 0; i < kThreadCount; i++) { pollfds[i].fd = listener_fds[i].get(); pollfds[i].events = POLLIN; } std::map portToFD; int received = 0; while (received < kConnectAttempts * 2) { ASSERT_THAT(poll(pollfds, kThreadCount, -1), SyscallSucceedsWithValue(Gt(0))); for (int i = 0; i < kThreadCount; i++) { if ((pollfds[i].revents & POLLIN) == 0) { continue; } received++; const int fd = pollfds[i].fd; struct sockaddr_storage addr = {}; socklen_t addrlen = sizeof(addr); int data; EXPECT_THAT(RetryEINTR(recvfrom)( fd, &data, sizeof(data), 0, reinterpret_cast(&addr), &addrlen), SyscallSucceedsWithValue(sizeof(data))); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(connector.family(), addr)); auto prev_port = portToFD.find(port); // Check that all packets from one client have been delivered to the // same server socket. if (prev_port == portToFD.end()) { portToFD[port] = fd; } else { EXPECT_EQ(portToFD[port], fd); } } } } INSTANTIATE_TEST_SUITE_P( All, SocketInetReusePortTest, ::testing::Values( // Listeners bound to IPv4 addresses refuse connections using IPv6 // addresses. TestParam{V4Any(), V4Loopback()}, TestParam{V4Loopback(), V4MappedLoopback()}, // Listeners bound to IN6ADDR_ANY accept all connections. TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V6Loopback()}, // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 // addresses. TestParam{V6Loopback(), V6Loopback()}), DescribeTestParam); struct ProtocolTestParam { std::string description; int type; }; std::string DescribeProtocolTestParam( ::testing::TestParamInfo const& info) { return info.param.description; } using SocketMultiProtocolInetLoopbackTest = ::testing::TestWithParam; TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) { auto const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 loopback on a dual stack socket. TestAddress const& test_addr_dual = V4MappedLoopback(); sockaddr_storage addr_dual = test_addr_dual.addr; const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_dual.family(), param.type, 0)); ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast(&addr_dual), test_addr_dual.addr_len), SyscallSucceeds()); // Get the port that we bound. socklen_t addrlen = test_addr_dual.addr_len; ASSERT_THAT(getsockname(fd_dual.get(), reinterpret_cast(&addr_dual), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); // Verify that we can still bind the v6 loopback on the same port. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); int ret = bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len); if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } ASSERT_THAT(ret, SyscallSucceeds()); // Verify that binding the v4 loopback with the same port on a v4 socket // fails. TestAddress const& test_addr_v4 = V4Loopback(); sockaddr_storage addr_v4 = test_addr_v4.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); const FileDescriptor fd_v4 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast(&addr_v4), test_addr_v4.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) { auto const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 any on a dual stack socket. TestAddress const& test_addr_dual = V4MappedAny(); sockaddr_storage addr_dual = test_addr_dual.addr; const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_dual.family(), param.type, 0)); ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast(&addr_dual), test_addr_dual.addr_len), SyscallSucceeds()); // Get the port that we bound. socklen_t addrlen = test_addr_dual.addr_len; ASSERT_THAT(getsockname(fd_dual.get(), reinterpret_cast(&addr_dual), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); // Verify that we can still bind the v6 loopback on the same port. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); int ret = bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len); if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } ASSERT_THAT(ret, SyscallSucceeds()); // Verify that binding the v4 loopback with the same port on a v4 socket // fails. TestAddress const& test_addr_v4 = V4Loopback(); sockaddr_storage addr_v4 = test_addr_v4.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); const FileDescriptor fd_v4 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast(&addr_v4), test_addr_v4.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) { auto const& param = GetParam(); // Bind the v6 any on a dual stack socket. TestAddress const& test_addr_dual = V6Any(); sockaddr_storage addr_dual = test_addr_dual.addr; const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0)); ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast(&addr_dual), test_addr_dual.addr_len), SyscallSucceeds()); // Get the port that we bound. socklen_t addrlen = test_addr_dual.addr_len; ASSERT_THAT(getsockname(fd_dual.get(), reinterpret_cast(&addr_dual), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); // Verify that binding the v6 loopback with the same port fails. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v4 loopback on the same port with a v6 socket // fails. TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port)); const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v4_mapped.family(), param.type, 0)); ASSERT_THAT( bind(fd_v4_mapped.get(), reinterpret_cast(&addr_v4_mapped), test_addr_v4_mapped.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v4 loopback on the same port with a v4 socket // fails. TestAddress const& test_addr_v4 = V4Loopback(); sockaddr_storage addr_v4 = test_addr_v4.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); const FileDescriptor fd_v4 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast(&addr_v4), test_addr_v4.addr_len), SyscallFailsWithErrno(EADDRINUSE)); } TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) { auto const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v6 any on a v6-only socket. TestAddress const& test_addr_dual = V6Any(); sockaddr_storage addr_dual = test_addr_dual.addr; const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_dual.family(), param.type, 0)); EXPECT_THAT(setsockopt(fd_dual.get(), IPPROTO_IPV6, IPV6_V6ONLY, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast(&addr_dual), test_addr_dual.addr_len), SyscallSucceeds()); // Get the port that we bound. socklen_t addrlen = test_addr_dual.addr_len; ASSERT_THAT(getsockname(fd_dual.get(), reinterpret_cast(&addr_dual), &addrlen), SyscallSucceeds()); uint16_t const port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); // Verify that binding the v6 loopback with the same port fails. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that we can still bind the v4 loopback on the same port. TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port)); const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v4_mapped.family(), param.type, 0)); int ret = bind(fd_v4_mapped.get(), reinterpret_cast(&addr_v4_mapped), test_addr_v4_mapped.addr_len); if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } ASSERT_THAT(ret, SyscallSucceeds()); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) { auto const& param = GetParam(); // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make // it disabled by default. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM); for (int i = 0; true; i++) { // Bind the v6 loopback on a dual stack socket. TestAddress const& test_addr = V6Loopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT( connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v6 loopback with the same port fails. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v4 any with the same port fails. TestAddress const& test_addr_v4_any = V4Any(); sockaddr_storage addr_v4_any = test_addr_v4_any.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, ephemeral_port)); const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v4_any.family(), param.type, 0)); ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast(&addr_v4_any), test_addr_v4_any.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that we can still bind the v4 loopback on the same port. TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, ephemeral_port)); const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v4_mapped.family(), param.type, 0)); int ret = bind(fd_v4_mapped.get(), reinterpret_cast(&addr_v4_mapped), test_addr_v4_mapped.addr_len); if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } EXPECT_THAT(ret, SyscallSucceeds()); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) { auto const& param = GetParam(); // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM); // Bind the v6 loopback on a dual stack socket. TestAddress const& test_addr = V6Loopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT(connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is not reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallSucceeds()); } TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) { auto const& param = GetParam(); // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make // it disabled by default. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM); for (int i = 0; true; i++) { // Bind the v4 loopback on a dual stack socket. TestAddress const& test_addr = V4MappedLoopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT( connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v4 loopback on the same port with a v4 socket // fails. TestAddress const& test_addr_v4 = V4Loopback(); sockaddr_storage addr_v4 = test_addr_v4.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v4.family(), &addr_v4, ephemeral_port)); const FileDescriptor fd_v4 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); EXPECT_THAT(bind(fd_v4.get(), reinterpret_cast(&addr_v4), test_addr_v4.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v6 any on the same port with a dual-stack socket // fails. TestAddress const& test_addr_v6_any = V6Any(); sockaddr_storage addr_v6_any = test_addr_v6_any.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port)); const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6_any.family(), param.type, 0)); ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast(&addr_v6_any), test_addr_v6_any.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we // tend to run out of ephemeral ports? Regardless, binding the v6 loopback // seems pretty reliable. Only try to bind the v6-only any on UDP and // gVisor. int ret = -1; if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) { // Verify that we can still bind the v6 loopback on the same port. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6.family(), param.type, 0)); ret = bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len); } else { // Verify that we can still bind the v6 any on the same port with a // v6-only socket. const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6_any.family(), param.type, 0)); EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ret = bind(fd_v6_only_any.get(), reinterpret_cast(&addr_v6_any), test_addr_v6_any.addr_len); } if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } EXPECT_THAT(ret, SyscallSucceeds()); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReservedResueAddr) { auto const& param = GetParam(); // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM); // Bind the v4 loopback on a dual stack socket. TestAddress const& test_addr = V4MappedLoopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT(connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is not reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallSucceeds()); } TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) { auto const& param = GetParam(); // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make // it disabled by default. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM); for (int i = 0; true; i++) { // Bind the v4 loopback on a v4 socket. TestAddress const& test_addr = V4Loopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT( connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v4 loopback on the same port with a v6 socket // fails. TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, ephemeral_port)); const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v4_mapped.family(), param.type, 0)); EXPECT_THAT( bind(fd_v4_mapped.get(), reinterpret_cast(&addr_v4_mapped), test_addr_v4_mapped.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // Verify that binding the v6 any on the same port with a dual-stack socket // fails. TestAddress const& test_addr_v6_any = V6Any(); sockaddr_storage addr_v6_any = test_addr_v6_any.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port)); const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6_any.family(), param.type, 0)); ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast(&addr_v6_any), test_addr_v6_any.addr_len), SyscallFailsWithErrno(EADDRINUSE)); // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we // tend to run out of ephemeral ports? Regardless, binding the v6 loopback // seems pretty reliable. Only try to bind the v6-only any on UDP and // gVisor. int ret = -1; if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) { // Verify that we can still bind the v6 loopback on the same port. TestAddress const& test_addr_v6 = V6Loopback(); sockaddr_storage addr_v6 = test_addr_v6.addr; ASSERT_NO_ERRNO( SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6.family(), param.type, 0)); ret = bind(fd_v6.get(), reinterpret_cast(&addr_v6), test_addr_v6.addr_len); } else { // Verify that we can still bind the v6 any on the same port with a // v6-only socket. const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE( Socket(test_addr_v6_any.family(), param.type, 0)); EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ret = bind(fd_v6_only_any.get(), reinterpret_cast(&addr_v6_any), test_addr_v6_any.addr_len); } if (ret == -1 && errno == EADDRINUSE) { // Port may have been in use. ASSERT_LT(i, 100); // Give up after 100 tries. continue; } EXPECT_THAT(ret, SyscallSucceeds()); // No need to try again. break; } } TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReservedReuseAddr) { auto const& param = GetParam(); // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets. SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM); // Bind the v4 loopback on a v4 socket. TestAddress const& test_addr = V4Loopback(); sockaddr_storage bound_addr = test_addr.addr; const FileDescriptor bound_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast(&bound_addr), test_addr.addr_len), SyscallSucceeds()); // Listen iff TCP. if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); } // Get the port that we bound. socklen_t bound_addr_len = test_addr.addr_len; ASSERT_THAT( getsockname(bound_fd.get(), reinterpret_cast(&bound_addr), &bound_addr_len), SyscallSucceeds()); // Connect to bind an ephemeral port. const FileDescriptor connected_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); ASSERT_THAT(connect(connected_fd.get(), reinterpret_cast(&bound_addr), bound_addr_len), SyscallSucceeds()); // Get the ephemeral port. sockaddr_storage connected_addr = {}; socklen_t connected_addr_len = sizeof(connected_addr); ASSERT_THAT(getsockname(connected_fd.get(), reinterpret_cast(&connected_addr), &connected_addr_len), SyscallSucceeds()); uint16_t const ephemeral_port = ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); // Verify that we actually got an ephemeral port. ASSERT_NE(ephemeral_port, 0); // Verify that the ephemeral port is not reserved. const FileDescriptor checking_fd = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, sizeof(kSockOptOn)), SyscallSucceeds()); EXPECT_THAT( bind(checking_fd.get(), reinterpret_cast(&connected_addr), connected_addr_len), SyscallSucceeds()); } TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) { auto const& param = GetParam(); TestAddress const& test_addr = V4Loopback(); sockaddr_storage addr = test_addr.addr; for (int i = 0; i < 2; i++) { const int portreuse1 = i % 2; auto s1 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); int fd1 = s1.get(); socklen_t addrlen = test_addr.addr_len; EXPECT_THAT( setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, &portreuse1, sizeof(int)), SyscallSucceeds()); ASSERT_THAT(bind(fd1, reinterpret_cast(&addr), addrlen), SyscallSucceeds()); ASSERT_THAT(getsockname(fd1, reinterpret_cast(&addr), &addrlen), SyscallSucceeds()); if (param.type == SOCK_STREAM) { ASSERT_THAT(listen(fd1, 1), SyscallSucceeds()); } // j is less than 4 to check that the port reuse logic works correctly after // closing bound sockets. for (int j = 0; j < 4; j++) { const int portreuse2 = j % 2; auto s2 = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); int fd2 = s2.get(); EXPECT_THAT( setsockopt(fd2, SOL_SOCKET, SO_REUSEPORT, &portreuse2, sizeof(int)), SyscallSucceeds()); std::cout << portreuse1 << " " << portreuse2; int ret = bind(fd2, reinterpret_cast(&addr), addrlen); // Verify that two sockets can be bound to the same port only if // SO_REUSEPORT is set for both of them. if (!portreuse1 || !portreuse2) { ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRINUSE)); } else { ASSERT_THAT(ret, SyscallSucceeds()); } } } } // Check that when a socket was bound to an address with REUSEPORT and then // closed, we can bind a different socket to the same address without needing // REUSEPORT. TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) { auto const& param = GetParam(); TestAddress const& test_addr = V4Loopback(); sockaddr_storage addr = test_addr.addr; auto s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); int fd = s.get(); socklen_t addrlen = test_addr.addr_len; int portreuse = 1; ASSERT_THAT( setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)), SyscallSucceeds()); ASSERT_THAT(bind(fd, reinterpret_cast(&addr), addrlen), SyscallSucceeds()); ASSERT_THAT(getsockname(fd, reinterpret_cast(&addr), &addrlen), SyscallSucceeds()); ASSERT_EQ(addrlen, test_addr.addr_len); s.reset(); // Open a new socket and bind to the same address, but w/o REUSEPORT. s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); fd = s.get(); portreuse = 0; ASSERT_THAT( setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)), SyscallSucceeds()); ASSERT_THAT(bind(fd, reinterpret_cast(&addr), addrlen), SyscallSucceeds()); } INSTANTIATE_TEST_SUITE_P( AllFamilies, SocketMultiProtocolInetLoopbackTest, ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, ProtocolTestParam{"UDP", SOCK_DGRAM}), DescribeProtocolTestParam); } // namespace } // namespace testing } // namespace gvisor