From 7743f313c3855fcc3d11f107843be56663203384 Mon Sep 17 00:00:00 2001 From: Zeling Feng Date: Tue, 16 May 2023 15:38:39 -0700 Subject: [PATCH] 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 --- pkg/tcpip/transport/tcp/endpoint.go | 3 + .../linux/socket_inet_loopback_isolated.cc | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index b9d1de679..ffd8eb415 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -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 } diff --git a/test/syscalls/linux/socket_inet_loopback_isolated.cc b/test/syscalls/linux/socket_inet_loopback_isolated.cc index 7c8f3d502..9f80c829e 100644 --- a/test/syscalls/linux/socket_inet_loopback_isolated.cc +++ b/test/syscalls/linux/socket_inet_loopback_isolated.cc @@ -13,7 +13,9 @@ // limitations under the License. #include +#include +#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);