TCPSocket::Connect() calls SocketAddress::GetAddressInfo() and tries to
connect any of them (in a for loop).
This used to work before commit 4f6d3a376c9f("[LLDB] Fix setting of
success in Socket::Close()") https://reviews.llvm.org/D116768.
As a side effect of that commit, TCPSocket can only connect to the first
address returned by SocketAddress::GetAddressInfo().
1. If the attempt to connect to the first address fails,
TCPSocket::Connect(), calls CLOSE_SOCKET(GetNativeSocket()), which
closes the fd, but DOES NOT set m_socket to kInvalidSocketValue.
2. On the second attempt, TCPSocket::CreateSocket() calls
Socket::Close().
3. Socket::Close() proceeds, because IsValid() is true (m_socket was not
reset on step 1).
4. Socket::Close() calls ::close(m_socket), which fails
5. Since commit 4f6d3a376c9f("[LLDB] Fix setting of success in
Socket::Close()"), this is error is detected. Socket::Close() returns
an error.
6. TCPSocket::CreateSocket() therefore returns an error.
7. TCPSocket::Connect() detects the error and continues, skipping the
second (and the third, fourth...) address.
This commit fixes the problem by changing step 1: by calling
Socket::Close, instead of directly calling close(m_socket), m_socket is
also se to kInvalidSocketValue. On step 3, Socket::Close() is going to
return immediately and, on step 6, TCPSocket::CreateSocket() does not
fail.
How to reproduce this problem:
On my system, getaddrinfo() resolves "localhost" to "::1" (first) and to
"127.0.0.1" (second).
Start a gdbserver that only listens on 127.0.0.1:
```
gdbserver 127.0.0.1:2159 /bin/cat
Process /bin/cat created; pid = 2146709
Listening on port 2159
```
Start lldb and make it connect to "localhost:2159"
```
./bin/lldb
(lldb) gdb-remote localhost:2159
```
Before 4f6d3a376c9f("[LLDB] Fix setting of success in Socket::Close()"),
this used to work. After that commit, it stopped working. This commit
fixes the problem.
Reviewed By: labath
Differential Revision: https://reviews.llvm.org/D126702
318 lines
9.3 KiB
C++
318 lines
9.3 KiB
C++
//===-- TCPSocket.cpp -----------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if defined(_MSC_VER)
|
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "lldb/Host/common/TCPSocket.h"
|
|
|
|
#include "lldb/Host/Config.h"
|
|
#include "lldb/Host/MainLoop.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/Log.h"
|
|
|
|
#include "llvm/Config/llvm-config.h"
|
|
#include "llvm/Support/Errno.h"
|
|
#include "llvm/Support/WindowsError.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#if LLDB_ENABLE_POSIX
|
|
#include <arpa/inet.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#include <winsock2.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#define CLOSE_SOCKET closesocket
|
|
typedef const char *set_socket_option_arg_type;
|
|
#else
|
|
#include <unistd.h>
|
|
#define CLOSE_SOCKET ::close
|
|
typedef const void *set_socket_option_arg_type;
|
|
#endif
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
static Status GetLastSocketError() {
|
|
std::error_code EC;
|
|
#ifdef _WIN32
|
|
EC = llvm::mapWindowsError(WSAGetLastError());
|
|
#else
|
|
EC = std::error_code(errno, std::generic_category());
|
|
#endif
|
|
return EC;
|
|
}
|
|
|
|
static const int kType = SOCK_STREAM;
|
|
|
|
TCPSocket::TCPSocket(bool should_close, bool child_processes_inherit)
|
|
: Socket(ProtocolTcp, should_close, child_processes_inherit) {}
|
|
|
|
TCPSocket::TCPSocket(NativeSocket socket, const TCPSocket &listen_socket)
|
|
: Socket(ProtocolTcp, listen_socket.m_should_close_fd,
|
|
listen_socket.m_child_processes_inherit) {
|
|
m_socket = socket;
|
|
}
|
|
|
|
TCPSocket::TCPSocket(NativeSocket socket, bool should_close,
|
|
bool child_processes_inherit)
|
|
: Socket(ProtocolTcp, should_close, child_processes_inherit) {
|
|
m_socket = socket;
|
|
}
|
|
|
|
TCPSocket::~TCPSocket() { CloseListenSockets(); }
|
|
|
|
bool TCPSocket::IsValid() const {
|
|
return m_socket != kInvalidSocketValue || m_listen_sockets.size() != 0;
|
|
}
|
|
|
|
// Return the port number that is being used by the socket.
|
|
uint16_t TCPSocket::GetLocalPortNumber() const {
|
|
if (m_socket != kInvalidSocketValue) {
|
|
SocketAddress sock_addr;
|
|
socklen_t sock_addr_len = sock_addr.GetMaxLength();
|
|
if (::getsockname(m_socket, sock_addr, &sock_addr_len) == 0)
|
|
return sock_addr.GetPort();
|
|
} else if (!m_listen_sockets.empty()) {
|
|
SocketAddress sock_addr;
|
|
socklen_t sock_addr_len = sock_addr.GetMaxLength();
|
|
if (::getsockname(m_listen_sockets.begin()->first, sock_addr,
|
|
&sock_addr_len) == 0)
|
|
return sock_addr.GetPort();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::string TCPSocket::GetLocalIPAddress() const {
|
|
// We bound to port zero, so we need to figure out which port we actually
|
|
// bound to
|
|
if (m_socket != kInvalidSocketValue) {
|
|
SocketAddress sock_addr;
|
|
socklen_t sock_addr_len = sock_addr.GetMaxLength();
|
|
if (::getsockname(m_socket, sock_addr, &sock_addr_len) == 0)
|
|
return sock_addr.GetIPAddress();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
uint16_t TCPSocket::GetRemotePortNumber() const {
|
|
if (m_socket != kInvalidSocketValue) {
|
|
SocketAddress sock_addr;
|
|
socklen_t sock_addr_len = sock_addr.GetMaxLength();
|
|
if (::getpeername(m_socket, sock_addr, &sock_addr_len) == 0)
|
|
return sock_addr.GetPort();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::string TCPSocket::GetRemoteIPAddress() const {
|
|
// We bound to port zero, so we need to figure out which port we actually
|
|
// bound to
|
|
if (m_socket != kInvalidSocketValue) {
|
|
SocketAddress sock_addr;
|
|
socklen_t sock_addr_len = sock_addr.GetMaxLength();
|
|
if (::getpeername(m_socket, sock_addr, &sock_addr_len) == 0)
|
|
return sock_addr.GetIPAddress();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string TCPSocket::GetRemoteConnectionURI() const {
|
|
if (m_socket != kInvalidSocketValue) {
|
|
return std::string(llvm::formatv(
|
|
"connect://[{0}]:{1}", GetRemoteIPAddress(), GetRemotePortNumber()));
|
|
}
|
|
return "";
|
|
}
|
|
|
|
Status TCPSocket::CreateSocket(int domain) {
|
|
Status error;
|
|
if (IsValid())
|
|
error = Close();
|
|
if (error.Fail())
|
|
return error;
|
|
m_socket = Socket::CreateSocket(domain, kType, IPPROTO_TCP,
|
|
m_child_processes_inherit, error);
|
|
return error;
|
|
}
|
|
|
|
Status TCPSocket::Connect(llvm::StringRef name) {
|
|
|
|
Log *log = GetLog(LLDBLog::Communication);
|
|
LLDB_LOGF(log, "TCPSocket::%s (host/port = %s)", __FUNCTION__, name.data());
|
|
|
|
Status error;
|
|
llvm::Expected<HostAndPort> host_port = DecodeHostAndPort(name);
|
|
if (!host_port)
|
|
return Status(host_port.takeError());
|
|
|
|
std::vector<SocketAddress> addresses =
|
|
SocketAddress::GetAddressInfo(host_port->hostname.c_str(), nullptr,
|
|
AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
|
|
for (SocketAddress &address : addresses) {
|
|
error = CreateSocket(address.GetFamily());
|
|
if (error.Fail())
|
|
continue;
|
|
|
|
address.SetPort(host_port->port);
|
|
|
|
if (-1 == llvm::sys::RetryAfterSignal(-1, ::connect, GetNativeSocket(),
|
|
&address.sockaddr(),
|
|
address.GetLength())) {
|
|
Close();
|
|
continue;
|
|
}
|
|
|
|
SetOptionNoDelay();
|
|
|
|
error.Clear();
|
|
return error;
|
|
}
|
|
|
|
error.SetErrorString("Failed to connect port");
|
|
return error;
|
|
}
|
|
|
|
Status TCPSocket::Listen(llvm::StringRef name, int backlog) {
|
|
Log *log = GetLog(LLDBLog::Connection);
|
|
LLDB_LOGF(log, "TCPSocket::%s (%s)", __FUNCTION__, name.data());
|
|
|
|
Status error;
|
|
llvm::Expected<HostAndPort> host_port = DecodeHostAndPort(name);
|
|
if (!host_port)
|
|
return Status(host_port.takeError());
|
|
|
|
if (host_port->hostname == "*")
|
|
host_port->hostname = "0.0.0.0";
|
|
std::vector<SocketAddress> addresses = SocketAddress::GetAddressInfo(
|
|
host_port->hostname.c_str(), nullptr, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
|
|
for (SocketAddress &address : addresses) {
|
|
int fd = Socket::CreateSocket(address.GetFamily(), kType, IPPROTO_TCP,
|
|
m_child_processes_inherit, error);
|
|
if (error.Fail())
|
|
continue;
|
|
|
|
// enable local address reuse
|
|
int option_value = 1;
|
|
set_socket_option_arg_type option_value_p =
|
|
reinterpret_cast<set_socket_option_arg_type>(&option_value);
|
|
::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, option_value_p,
|
|
sizeof(option_value));
|
|
|
|
SocketAddress listen_address = address;
|
|
if(!listen_address.IsLocalhost())
|
|
listen_address.SetToAnyAddress(address.GetFamily(), host_port->port);
|
|
else
|
|
listen_address.SetPort(host_port->port);
|
|
|
|
int err =
|
|
::bind(fd, &listen_address.sockaddr(), listen_address.GetLength());
|
|
if (-1 != err)
|
|
err = ::listen(fd, backlog);
|
|
|
|
if (-1 == err) {
|
|
error = GetLastSocketError();
|
|
CLOSE_SOCKET(fd);
|
|
continue;
|
|
}
|
|
|
|
if (host_port->port == 0) {
|
|
socklen_t sa_len = address.GetLength();
|
|
if (getsockname(fd, &address.sockaddr(), &sa_len) == 0)
|
|
host_port->port = address.GetPort();
|
|
}
|
|
m_listen_sockets[fd] = address;
|
|
}
|
|
|
|
if (m_listen_sockets.empty()) {
|
|
assert(error.Fail());
|
|
return error;
|
|
}
|
|
return Status();
|
|
}
|
|
|
|
void TCPSocket::CloseListenSockets() {
|
|
for (auto socket : m_listen_sockets)
|
|
CLOSE_SOCKET(socket.first);
|
|
m_listen_sockets.clear();
|
|
}
|
|
|
|
Status TCPSocket::Accept(Socket *&conn_socket) {
|
|
Status error;
|
|
if (m_listen_sockets.size() == 0) {
|
|
error.SetErrorString("No open listening sockets!");
|
|
return error;
|
|
}
|
|
|
|
int sock = -1;
|
|
int listen_sock = -1;
|
|
lldb_private::SocketAddress AcceptAddr;
|
|
MainLoop accept_loop;
|
|
std::vector<MainLoopBase::ReadHandleUP> handles;
|
|
for (auto socket : m_listen_sockets) {
|
|
auto fd = socket.first;
|
|
auto inherit = this->m_child_processes_inherit;
|
|
auto io_sp = IOObjectSP(new TCPSocket(socket.first, false, inherit));
|
|
handles.emplace_back(accept_loop.RegisterReadObject(
|
|
io_sp, [fd, inherit, &sock, &AcceptAddr, &error,
|
|
&listen_sock](MainLoopBase &loop) {
|
|
socklen_t sa_len = AcceptAddr.GetMaxLength();
|
|
sock = AcceptSocket(fd, &AcceptAddr.sockaddr(), &sa_len, inherit,
|
|
error);
|
|
listen_sock = fd;
|
|
loop.RequestTermination();
|
|
}, error));
|
|
if (error.Fail())
|
|
return error;
|
|
}
|
|
|
|
bool accept_connection = false;
|
|
std::unique_ptr<TCPSocket> accepted_socket;
|
|
// Loop until we are happy with our connection
|
|
while (!accept_connection) {
|
|
accept_loop.Run();
|
|
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
lldb_private::SocketAddress &AddrIn = m_listen_sockets[listen_sock];
|
|
if (!AddrIn.IsAnyAddr() && AcceptAddr != AddrIn) {
|
|
CLOSE_SOCKET(sock);
|
|
llvm::errs() << llvm::formatv(
|
|
"error: rejecting incoming connection from {0} (expecting {1})",
|
|
AcceptAddr.GetIPAddress(), AddrIn.GetIPAddress());
|
|
continue;
|
|
}
|
|
accept_connection = true;
|
|
accepted_socket.reset(new TCPSocket(sock, *this));
|
|
}
|
|
|
|
if (!accepted_socket)
|
|
return error;
|
|
|
|
// Keep our TCP packets coming without any delays.
|
|
accepted_socket->SetOptionNoDelay();
|
|
error.Clear();
|
|
conn_socket = accepted_socket.release();
|
|
return error;
|
|
}
|
|
|
|
int TCPSocket::SetOptionNoDelay() {
|
|
return SetOption(IPPROTO_TCP, TCP_NODELAY, 1);
|
|
}
|
|
|
|
int TCPSocket::SetOptionReuseAddress() {
|
|
return SetOption(SOL_SOCKET, SO_REUSEADDR, 1);
|
|
}
|