mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
All inbound segments for connections in ESTABLISHED state are delivered to the endpoint's queue but for every segment delivered we also queue the endpoint for processing to a selected processor. This ensures that when there are a large number of connections in ESTABLISHED state the inbound packets are all handled by a small number of goroutines and significantly reduces the amount of work the goscheduler has to perform. We let connections in other states follow the current path where the endpoint's goroutine directly handles the segments. Updates #231 PiperOrigin-RevId: 289728325
2104 lines
81 KiB
C++
2104 lines
81 KiB
C++
// 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 <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <poll.h>
|
|
#include <string.h>
|
|
|
|
#include <atomic>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#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<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
|
|
switch (family) {
|
|
case AF_INET:
|
|
return static_cast<uint16_t>(
|
|
reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
|
|
case AF_INET6:
|
|
return static_cast<uint16_t>(
|
|
reinterpret_cast<sockaddr_in6 const*>(&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<sockaddr_in*>(addr)->sin_port = port;
|
|
return NoError();
|
|
case AF_INET6:
|
|
reinterpret_cast<sockaddr_in6*>(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<TestParam> const& info) {
|
|
return absl::StrCat("Listen", info.param.listener.description, "_Connect",
|
|
info.param.connector.description);
|
|
}
|
|
|
|
using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
|
|
|
|
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<Operation>;
|
|
|
|
using DualStackSocketTest =
|
|
::testing::TestWithParam<std::tuple<TestAddress, OperationSequence>>;
|
|
|
|
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<const sockaddr*>(&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<sockaddr_storage*>(&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<sockaddr_storage*>(&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<sockaddr_storage*>(&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<sockaddr*>(&sock_addr),
|
|
&addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6));
|
|
|
|
auto sock_addr_in6 = reinterpret_cast<const sockaddr_in6*>(&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<sockaddr*>(&sock_addr));
|
|
|
|
EXPECT_NE(sock_addr_in6->sin6_port, 0);
|
|
} else if (IN6_IS_ADDR_V4MAPPED(
|
|
reinterpret_cast<const sockaddr_in6*>(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<sockaddr*>(&sock_addr));
|
|
}
|
|
}
|
|
|
|
if (peername) {
|
|
sockaddr_storage peer_addr;
|
|
socklen_t addrlen = sizeof(peer_addr);
|
|
ASSERT_THAT(getpeername(fd.get(), reinterpret_cast<sockaddr*>(&peer_addr),
|
|
&addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6));
|
|
|
|
if (addr.family() == AF_INET ||
|
|
IN6_IS_ADDR_V4MAPPED(reinterpret_cast<const sockaddr_in6*>(addr_in)
|
|
->sin6_addr.s6_addr32)) {
|
|
EXPECT_TRUE(IN6_IS_ADDR_V4MAPPED(
|
|
reinterpret_cast<const sockaddr_in6*>(&peer_addr)
|
|
->sin6_addr.s6_addr32))
|
|
<< OperationToString(operation) << " getpeername="
|
|
<< GetAddrStr(reinterpret_cast<sockaddr*>(&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<OperationSequence>(
|
|
{{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<TestAddress, OperationSequence>> 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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<ScopedThread> 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<ScopedThread>([&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&conn_bound_addr),
|
|
conn_addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
|
reinterpret_cast<sockaddr*>(&conn_addr),
|
|
conn_addrlen),
|
|
SyscallFailsWithErrno(EADDRINUSE));
|
|
} else {
|
|
ASSERT_THAT(
|
|
bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&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<sockaddr*>(&conn_bound_addr),
|
|
conn_addrlen),
|
|
SyscallSucceeds());
|
|
}
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
|
reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&conn_bound_addr), conn_addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
|
reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&conn_bound_addr), conn_addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
|
reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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);
|
|
}
|
|
|
|
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<TestParam>;
|
|
|
|
// 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<sockaddr*>(&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<sockaddr*>(&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<int> connects_received = ATOMIC_VAR_INIT(0);
|
|
std::unique_ptr<ScopedThread> 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<ScopedThread>(
|
|
[&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<int> packets_received = ATOMIC_VAR_INIT(0);
|
|
std::unique_ptr<ScopedThread> 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<ScopedThread>(
|
|
[&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<struct sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<uint16_t, int> 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<struct sockaddr*>(&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<ProtocolTestParam> const& info) {
|
|
return info.param.description;
|
|
}
|
|
|
|
using SocketMultiProtocolInetLoopbackTest =
|
|
::testing::TestWithParam<ProtocolTestParam>;
|
|
|
|
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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&addr), addrlen),
|
|
SyscallSucceeds());
|
|
|
|
ASSERT_THAT(getsockname(fd1, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&addr), addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(getsockname(fd, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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
|