Add a syscall test to test address conflicts during connect

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
This commit is contained in:
Zeling Feng
2023-05-16 15:41:24 -07:00
committed by gVisor bot
parent e012937a29
commit 7743f313c3
2 changed files with 64 additions and 0 deletions
+3
View File
@@ -2425,6 +2425,9 @@ func (e *endpoint) connect(addr tcpip.FullAddress, handshake bool) tcpip.Error {
e.setEndpointState(StateConnecting)
if err := e.registerEndpoint(addr, netProto, r.NICID()); err != nil {
e.setEndpointState(oldState)
if _, ok := err.(*tcpip.ErrPortInUse); ok {
return &tcpip.ErrBadLocalAddress{}
}
return err
}
@@ -13,7 +13,9 @@
// 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"
@@ -291,6 +293,65 @@ TEST_P(SocketInetLoopbackIsolatedTest, TCPLinger2TimeoutAfterClose) {
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);