mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
7743f313c3
When SO_REUSEADDR is enabled, it is possible to create two connections with the same 4-tuple, the error is only reported when `connect` instead of `bind`, also the reported errno is `EADDRNOTAVAIL` instead of `EADDRINUSE`. PiperOrigin-RevId: 532593923
594 lines
24 KiB
C++
594 lines
24 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 <netinet/tcp.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "absl/time/clock.h"
|
|
#include "absl/time/time.h"
|
|
#include "test/syscalls/linux/socket_inet_loopback_test_params.h"
|
|
#include "test/util/capability_util.h"
|
|
#include "test/util/socket_util.h"
|
|
#include "test/util/test_util.h"
|
|
|
|
// Unit tests in this file will run in their own network namespace.
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
using SocketInetLoopbackIsolatedTest =
|
|
::testing::TestWithParam<SocketInetTestParam>;
|
|
|
|
TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitTest) {
|
|
SocketInetTestParam const& param = GetParam();
|
|
sockaddr_storage listen_addr, conn_bound_addr;
|
|
listen_addr = param.listener.addr;
|
|
SetupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/,
|
|
false /*accept_close*/, &listen_addr, &conn_bound_addr);
|
|
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
|
|
ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
|
|
param.connector.addr_len),
|
|
SyscallFailsWithErrno(EADDRINUSE));
|
|
}
|
|
|
|
TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitReuseTest) {
|
|
SocketInetTestParam const& param = GetParam();
|
|
sockaddr_storage listen_addr, conn_bound_addr;
|
|
listen_addr = param.listener.addr;
|
|
SetupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/,
|
|
false /*accept_close*/, &listen_addr, &conn_bound_addr);
|
|
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
|
|
param.connector.addr_len),
|
|
SyscallFailsWithErrno(EADDRINUSE));
|
|
}
|
|
|
|
// These tests are disabled under random save as 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 re-binding of client and server bound addresses when the older
|
|
// connection is in TIME_WAIT.
|
|
TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitTest) {
|
|
SocketInetTestParam const& param = GetParam();
|
|
sockaddr_storage listen_addr, conn_bound_addr;
|
|
listen_addr = param.listener.addr;
|
|
SetupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/,
|
|
true /*accept_close*/, &listen_addr, &conn_bound_addr);
|
|
|
|
// Now bind 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_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
|
|
param.connector.addr_len),
|
|
SyscallSucceeds());
|
|
|
|
FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(
|
|
bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
|
|
SyscallFailsWithErrno(EADDRINUSE));
|
|
}
|
|
|
|
TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitReuseTest) {
|
|
SocketInetTestParam const& param = GetParam();
|
|
sockaddr_storage listen_addr, conn_bound_addr;
|
|
listen_addr = param.listener.addr;
|
|
SetupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/,
|
|
true /*accept_close*/, &listen_addr, &conn_bound_addr);
|
|
|
|
FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(
|
|
bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
|
|
|
// Now bind and connect 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_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
|
|
param.connector.addr_len),
|
|
SyscallSucceeds());
|
|
|
|
uint16_t const port =
|
|
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr));
|
|
sockaddr_storage conn_addr = param.connector.addr;
|
|
ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port));
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
|
|
param.connector.addr_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// 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(SocketInetLoopbackIsolatedTest, TCPFinWait2Test) {
|
|
SocketInetTestParam 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(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&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;
|
|
|
|
ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&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();
|
|
|
|
ASSERT_THAT(
|
|
RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// TCPLinger2TimeoutAfterClose creates a pair of connected sockets
|
|
// then closes one end to trigger FIN_WAIT2 state for the closed endpoint.
|
|
// 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(SocketInetLoopbackIsolatedTest, TCPLinger2TimeoutAfterClose) {
|
|
SocketInetTestParam 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(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&conn_bound_addr), &conn_addrlen),
|
|
SyscallSucceeds());
|
|
|
|
// Disable cooperative saves after this point as TCP timers are not restored
|
|
// across a S/R.
|
|
{
|
|
DisableSave ds;
|
|
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));
|
|
|
|
// ds going out of scope will Re-enable S/R's since at this point the timer
|
|
// must have fired and cleaned up the endpoint.
|
|
}
|
|
|
|
// 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(), AsSockAddr(&conn_bound_addr), conn_addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(
|
|
RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(SocketInetLoopbackIsolatedTest, TCPConnectionReuseAddrConflicts) {
|
|
SocketInetTestParam const& param = GetParam();
|
|
TestAddress const& listener = param.listener;
|
|
TestAddress const& connector = param.connector;
|
|
|
|
const FileDescriptor listen_fd =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_STREAM, 0));
|
|
|
|
sockaddr_storage listen_addr = listener.addr;
|
|
ASSERT_THAT(
|
|
bind(listen_fd.get(), AsSockAddr(&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(), AsSockAddr(&listen_addr), &addrlen),
|
|
SyscallSucceeds());
|
|
|
|
const uint16_t port =
|
|
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
|
|
|
// Create a first connection.
|
|
FileDescriptor conn_fd1 = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(setsockopt(conn_fd1.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
|
|
sockaddr_storage conn_addr = connector.addr;
|
|
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd1.get(), AsSockAddr(&conn_addr),
|
|
connector.addr_len),
|
|
SyscallSucceeds());
|
|
sockaddr_storage conn_bound_addr;
|
|
addrlen = sizeof(conn_bound_addr);
|
|
ASSERT_THAT(
|
|
getsockname(conn_fd1.get(), AsSockAddr(&conn_bound_addr), &addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(addrlen, connector.addr_len);
|
|
|
|
// Create the second connection that is bind to the same local address as the
|
|
// first.
|
|
FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
|
ASSERT_THAT(setsockopt(conn_fd2.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
// Bind should succeed.
|
|
ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), addrlen),
|
|
SyscallSucceeds());
|
|
|
|
// Connect should fail.
|
|
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
|
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr),
|
|
connector.addr_len),
|
|
SyscallFailsWithErrno(EADDRNOTAVAIL));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackIsolatedTest,
|
|
SocketInetLoopbackTestValues(),
|
|
DescribeSocketInetTestParam);
|
|
|
|
using SocketMultiProtocolInetLoopbackIsolatedTest =
|
|
::testing::TestWithParam<ProtocolTestParam>;
|
|
|
|
TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest, BindToDeviceReusePort) {
|
|
// setsockopt(SO_BINDTODEVICE) requires CAP_NET_RAW.
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
|
|
|
|
ProtocolTestParam const& param = GetParam();
|
|
TestAddress const& test_addr = V4Loopback();
|
|
|
|
auto socket1 =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
|
|
auto socket2 =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
|
|
|
|
const char kLoopbackDeviceName[] = "lo";
|
|
|
|
// Bind socket1 with REUSEPORT and BINDTODEVICE.
|
|
ASSERT_THAT(setsockopt(socket1.get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(setsockopt(socket1.get(), SOL_SOCKET, SO_BINDTODEVICE,
|
|
kLoopbackDeviceName, strlen(kLoopbackDeviceName)),
|
|
SyscallSucceeds());
|
|
|
|
// Bind the first socket to the loopback and take note of the selected port.
|
|
auto addr = V4Loopback();
|
|
ASSERT_THAT(bind(socket1.get(), AsSockAddr(&addr.addr), addr.addr_len),
|
|
SyscallSucceeds());
|
|
socklen_t addr_len = addr.addr_len;
|
|
ASSERT_THAT(getsockname(socket1.get(), AsSockAddr(&addr.addr), &addr_len),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addr_len, addr.addr_len);
|
|
|
|
// Bind socket2 to the same device and address as socket1.
|
|
ASSERT_THAT(setsockopt(socket2.get(), SOL_SOCKET, SO_BINDTODEVICE,
|
|
kLoopbackDeviceName, strlen(kLoopbackDeviceName)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(setsockopt(socket2.get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(bind(socket2.get(), AsSockAddr(&addr.addr), addr.addr_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
|
|
V4EphemeralPortReservedReuseAddr) {
|
|
ProtocolTestParam const& param = GetParam();
|
|
|
|
// 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(), AsSockAddr(&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(), AsSockAddr(&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(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&connected_addr), connected_addr_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
|
|
V4MappedEphemeralPortReservedReuseAddr) {
|
|
ProtocolTestParam const& param = GetParam();
|
|
|
|
// 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(), AsSockAddr(&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(), AsSockAddr(&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(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&connected_addr), connected_addr_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
|
|
V6EphemeralPortReservedReuseAddr) {
|
|
ProtocolTestParam const& param = GetParam();
|
|
|
|
// 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(), AsSockAddr(&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(), AsSockAddr(&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(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&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(), AsSockAddr(&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(), AsSockAddr(&connected_addr), connected_addr_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AllFamilies,
|
|
SocketMultiProtocolInetLoopbackIsolatedTest,
|
|
ProtocolTestValues(), DescribeProtocolTestParam);
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|