to reflect the new license. We understand that people may be surprised that we're moving the header entirely to discuss the new license. We checked this carefully with the Foundation's lawyer and we believe this is the correct approach. Essentially, all code in the project is now made available by the LLVM project under our new license, so you will see that the license headers include that license only. Some of our contributors have contributed code under our old license, and accordingly, we have retained a copy of our old license notice in the top-level files in each project and repository. llvm-svn: 351636
392 lines
11 KiB
C++
392 lines
11 KiB
C++
//===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
bool any_addr = (strcmp(listen_host, "*") == 0);
|
|
|
|
// If the user wants to allow connections from any address we should create
|
|
// sockets on all families that can resolve localhost. This will allow us to
|
|
// listen for IPv6 and IPv4 connections from all addresses if those interfaces
|
|
// are available.
|
|
const char *local_addr = any_addr ? "localhost" : listen_host;
|
|
|
|
std::map<int, lldb_private::SocketAddress> sockets;
|
|
auto addresses = lldb_private::SocketAddress::GetAddressInfo(
|
|
local_addr, 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);
|
|
|
|
lldb_private::SocketAddress bind_address = address;
|
|
|
|
if(any_addr || !bind_address.IsLocalhost())
|
|
bind_address.SetToAnyAddress(bind_address.GetFamily(), port);
|
|
else
|
|
bind_address.SetPort(port);
|
|
|
|
int error =
|
|
::bind(sock_fd, &bind_address.sockaddr(), bind_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());
|
|
err.Clear();
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
(const char *)
|
|
buffer); // All data is string based in debugserver, so this is safe
|
|
DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", (int)length,
|
|
(const 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;
|
|
}
|