Files
clang-p2996/lldb/tools/debugserver/source/RNBSocket.cpp
Chris Bieneman 1182779917 Re-landing IPv6 support for LLDB Host
This support was landed in r300579, and reverted in r300669 due to failures on the bots.

The failures were caused by sockets not being properly closed, and this updated version of the patches should resolve that.

Summary from the original change:

This patch adds IPv6 support to LLDB/Host's TCP socket implementation. Supporting IPv6 involved a few significant changes to the implementation of the socket layers, and I have performed some significant code cleanup along the way.

This patch changes the Socket constructors for all types of sockets to not create sockets until first use. This is required for IPv6 support because the socket type will vary based on the address you are connecting to. This also has the benefit of removing code that could have errors from the Socket subclass constructors (which seems like a win to me).

The patch also slightly changes the API and behaviors of the Listen/Accept pattern. Previously both Listen and Accept calls took an address specified as a string. Now only listen does. This change was made because the Listen call can result in opening more than one socket. In order to support listening for both IPv4 and IPv6 connections we need to open one AF_INET socket and one AF_INET6 socket. During the listen call we construct a map of file descriptors to addrin structures which represent the allowable incoming connection address. This map removes the need for taking an address into the Accept call.

This does have a change in functionality. Previously you could Listen for connections based on one address, and Accept connections from a different address. This is no longer supported. I could not find anywhere in LLDB where we actually used the APIs in that way. The new API does still support AnyAddr for allowing incoming connections from any address.

The Listen implementation is implemented using kqueue on FreeBSD and Darwin, WSAPoll on Windows and poll(2) everywhere else.

https://reviews.llvm.org/D31823

llvm-svn: 301492
2017-04-26 23:17:20 +00:00

377 lines
10 KiB
C++

//===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Created by Greg Clayton on 12/12/07.
//
//===----------------------------------------------------------------------===//
#include "RNBSocket.h"
#include "DNBError.h"
#include "DNBLog.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <map>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/event.h>
#include <termios.h>
#include <vector>
#include "lldb/Host/SocketAddress.h"
#ifdef WITH_LOCKDOWN
#include "lockdown.h"
#endif
/* Once we have a RNBSocket object with a port # specified,
this function is called to wait for an incoming connection.
This function blocks while waiting for that connection. */
bool ResolveIPV4HostName(const char *hostname, in_addr_t &addr) {
if (hostname == NULL || hostname[0] == '\0' ||
strcmp(hostname, "localhost") == 0 ||
strcmp(hostname, "127.0.0.1") == 0) {
addr = htonl(INADDR_LOOPBACK);
return true;
} else if (strcmp(hostname, "*") == 0) {
addr = htonl(INADDR_ANY);
return true;
} else {
// See if an IP address was specified as numbers
int inet_pton_result = ::inet_pton(AF_INET, hostname, &addr);
if (inet_pton_result == 1)
return true;
struct hostent *host_entry = gethostbyname(hostname);
if (host_entry) {
std::string ip_str(
::inet_ntoa(*(struct in_addr *)*host_entry->h_addr_list));
inet_pton_result = ::inet_pton(AF_INET, ip_str.c_str(), &addr);
if (inet_pton_result == 1)
return true;
}
}
return false;
}
rnb_err_t RNBSocket::Listen(const char *listen_host, uint16_t port,
PortBoundCallback callback,
const void *callback_baton) {
// DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s called",
// (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__);
// Disconnect without saving errno
Disconnect(false);
DNBError err;
int queue_id = kqueue();
if (queue_id < 0) {
err.SetError(errno, DNBError::MachKernel);
err.LogThreaded("error: failed to create kqueue.");
return rnb_err;
}
std::map<int, lldb_private::SocketAddress> sockets;
auto addresses = lldb_private::SocketAddress::GetAddressInfo(
listen_host, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
for (auto address : addresses) {
int sock_fd = ::socket(address.GetFamily(), SOCK_STREAM, IPPROTO_TCP);
if (sock_fd == -1)
continue;
SetSocketOption(sock_fd, SOL_SOCKET, SO_REUSEADDR, 1);
address.SetPort(port);
int error = ::bind(sock_fd, &address.sockaddr(), address.GetLength());
if (error == -1) {
ClosePort(sock_fd, false);
continue;
}
error = ::listen(sock_fd, 5);
if (error == -1) {
ClosePort(sock_fd, false);
continue;
}
// We were asked to listen on port zero which means we must now read the
// actual port that was given to us as port zero is a special code for "find
// an open port for me". This will only execute on the first socket created,
// subesquent sockets will reuse this port number.
if (port == 0) {
socklen_t sa_len = address.GetLength();
if (getsockname(sock_fd, &address.sockaddr(), &sa_len) == 0)
port = address.GetPort();
}
sockets[sock_fd] = address;
}
if (sockets.size() == 0) {
err.SetError(errno, DNBError::POSIX);
err.LogThreaded("::listen or ::bind failed");
return rnb_err;
}
if (callback)
callback(callback_baton, port);
std::vector<struct kevent> events;
events.resize(sockets.size());
int i = 0;
for (auto socket : sockets) {
EV_SET(&events[i++], socket.first, EVFILT_READ, EV_ADD, 0, 0, 0);
}
bool accept_connection = false;
// Loop until we are happy with our connection
while (!accept_connection) {
struct kevent event_list[4];
int num_events =
kevent(queue_id, events.data(), events.size(), event_list, 4, NULL);
if (num_events < 0) {
err.SetError(errno, DNBError::MachKernel);
err.LogThreaded("error: kevent() failed.");
}
for (int i = 0; i < num_events; ++i) {
auto sock_fd = event_list[i].ident;
auto socket_pair = sockets.find(sock_fd);
if (socket_pair == sockets.end())
continue;
lldb_private::SocketAddress &addr_in = socket_pair->second;
lldb_private::SocketAddress accept_addr;
socklen_t sa_len = accept_addr.GetMaxLength();
m_fd = ::accept(sock_fd, &accept_addr.sockaddr(), &sa_len);
if (m_fd == -1) {
err.SetError(errno, DNBError::POSIX);
err.LogThreaded("error: Socket accept failed.");
}
if (addr_in.IsAnyAddr())
accept_connection = true;
else {
if (accept_addr == addr_in)
accept_connection = true;
else {
::close(m_fd);
m_fd = -1;
::fprintf(
stderr,
"error: rejecting incoming connection from %s (expecting %s)\n",
accept_addr.GetIPAddress().c_str(),
addr_in.GetIPAddress().c_str());
DNBLogThreaded("error: rejecting connection from %s (expecting %s)\n",
accept_addr.GetIPAddress().c_str(),
addr_in.GetIPAddress().c_str());
}
}
}
if (err.Fail())
break;
}
for (auto socket : sockets) {
int ListenFd = socket.first;
ClosePort(ListenFd, false);
}
if (err.Fail())
return rnb_err;
// Keep our TCP packets coming without any delays.
SetSocketOption(m_fd, IPPROTO_TCP, TCP_NODELAY, 1);
return rnb_success;
}
rnb_err_t RNBSocket::Connect(const char *host, uint16_t port) {
auto result = rnb_err;
Disconnect(false);
auto addresses = lldb_private::SocketAddress::GetAddressInfo(
host, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
for (auto address : addresses) {
m_fd = ::socket(address.GetFamily(), SOCK_STREAM, IPPROTO_TCP);
if (m_fd == -1)
continue;
// Enable local address reuse
SetSocketOption(m_fd, SOL_SOCKET, SO_REUSEADDR, 1);
address.SetPort(port);
if (-1 == ::connect(m_fd, &address.sockaddr(), address.GetLength())) {
Disconnect(false);
continue;
}
SetSocketOption(m_fd, IPPROTO_TCP, TCP_NODELAY, 1);
result = rnb_success;
break;
}
return result;
}
rnb_err_t RNBSocket::useFD(int fd) {
if (fd < 0) {
DNBLogThreadedIf(LOG_RNB_COMM, "Bad file descriptor passed in.");
return rnb_err;
}
m_fd = fd;
return rnb_success;
}
#ifdef WITH_LOCKDOWN
rnb_err_t RNBSocket::ConnectToService() {
DNBLog("Connecting to com.apple.%s service...", DEBUGSERVER_PROGRAM_NAME);
// Disconnect from any previous connections
Disconnect(false);
if (::secure_lockdown_checkin(&m_ld_conn, NULL, NULL) != kLDESuccess) {
DNBLogThreadedIf(LOG_RNB_COMM,
"::secure_lockdown_checkin(&m_fd, NULL, NULL) failed");
m_fd = -1;
return rnb_not_connected;
}
m_fd = ::lockdown_get_socket(m_ld_conn);
if (m_fd == -1) {
DNBLogThreadedIf(LOG_RNB_COMM, "::lockdown_get_socket() failed");
return rnb_not_connected;
}
m_fd_from_lockdown = true;
return rnb_success;
}
#endif
rnb_err_t RNBSocket::OpenFile(const char *path) {
DNBError err;
m_fd = open(path, O_RDWR);
if (m_fd == -1) {
err.SetError(errno, DNBError::POSIX);
err.LogThreaded("can't open file '%s'", path);
return rnb_not_connected;
} else {
struct termios stdin_termios;
if (::tcgetattr(m_fd, &stdin_termios) == 0) {
stdin_termios.c_lflag &= ~ECHO; // Turn off echoing
stdin_termios.c_lflag &= ~ICANON; // Get one char at a time
::tcsetattr(m_fd, TCSANOW, &stdin_termios);
}
}
return rnb_success;
}
int RNBSocket::SetSocketOption(int fd, int level, int option_name,
int option_value) {
return ::setsockopt(fd, level, option_name, &option_value,
sizeof(option_value));
}
rnb_err_t RNBSocket::Disconnect(bool save_errno) {
#ifdef WITH_LOCKDOWN
if (m_fd_from_lockdown) {
m_fd_from_lockdown = false;
m_fd = -1;
lockdown_disconnect(m_ld_conn);
return rnb_success;
}
#endif
return ClosePort(m_fd, save_errno);
}
rnb_err_t RNBSocket::Read(std::string &p) {
char buf[1024];
p.clear();
// Note that BUF is on the stack so we must be careful to keep any
// writes to BUF from overflowing or we'll have security issues.
if (m_fd == -1)
return rnb_err;
// DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s calling read()",
// (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__);
DNBError err;
ssize_t bytesread = read(m_fd, buf, sizeof(buf));
if (bytesread <= 0)
err.SetError(errno, DNBError::POSIX);
else
p.append(buf, bytesread);
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
err.LogThreaded("::read ( %i, %p, %llu ) => %i", m_fd, buf, sizeof(buf),
(uint64_t)bytesread);
// Our port went away - we have to mark this so IsConnected will return the
// truth.
if (bytesread == 0) {
m_fd = -1;
return rnb_not_connected;
} else if (bytesread == -1) {
m_fd = -1;
return rnb_err;
}
// Strip spaces from the end of the buffer
while (!p.empty() && isspace(p[p.size() - 1]))
p.erase(p.size() - 1);
// Most data in the debugserver packets valid printable characters...
DNBLogThreadedIf(LOG_RNB_COMM, "read: %s", p.c_str());
return rnb_success;
}
rnb_err_t RNBSocket::Write(const void *buffer, size_t length) {
if (m_fd == -1)
return rnb_err;
DNBError err;
ssize_t bytessent = write(m_fd, buffer, length);
if (bytessent < 0)
err.SetError(errno, DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
err.LogThreaded("::write ( socket = %i, buffer = %p, length = %llu) => %i",
m_fd, buffer, length, (uint64_t)bytessent);
if (bytessent < 0)
return rnb_err;
if ((size_t)bytessent != length)
return rnb_err;
DNBLogThreadedIf(
LOG_RNB_PACKETS, "putpkt: %*s", (int)length,
(char *)
buffer); // All data is string based in debugserver, so this is safe
DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", (int)length, (char *)buffer);
return rnb_success;
}
rnb_err_t RNBSocket::ClosePort(int &fd, bool save_errno) {
int close_err = 0;
if (fd > 0) {
errno = 0;
close_err = close(fd);
fd = -1;
}
return close_err != 0 ? rnb_err : rnb_success;
}