
*** to conform to clang-format’s LLVM style. This kind of mass change has *** two obvious implications: Firstly, merging this particular commit into a downstream fork may be a huge effort. Alternatively, it may be worth merging all changes up to this commit, performing the same reformatting operation locally, and then discarding the merge for this particular commit. The commands used to accomplish this reformatting were as follows (with current working directory as the root of the repository): find . \( -iname "*.c" -or -iname "*.cpp" -or -iname "*.h" -or -iname "*.mm" \) -exec clang-format -i {} + find . -iname "*.py" -exec autopep8 --in-place --aggressive --aggressive {} + ; The version of clang-format used was 3.9.0, and autopep8 was 1.2.4. Secondly, “blame” style tools will generally point to this commit instead of a meaningful prior commit. There are alternatives available that will attempt to look through this change and find the appropriate prior commit. YMMV. llvm-svn: 280751
387 lines
11 KiB
C++
387 lines
11 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 <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <termios.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);
|
|
|
|
// Now figure out the hostname that will be attaching and palce it into
|
|
struct sockaddr_in listen_addr;
|
|
::memset(&listen_addr, 0, sizeof listen_addr);
|
|
listen_addr.sin_len = sizeof listen_addr;
|
|
listen_addr.sin_family = AF_INET;
|
|
listen_addr.sin_port = htons(port);
|
|
listen_addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (!ResolveIPV4HostName(listen_host, listen_addr.sin_addr.s_addr)) {
|
|
DNBLogThreaded("error: failed to resolve connecting host '%s'",
|
|
listen_host);
|
|
return rnb_err;
|
|
}
|
|
|
|
DNBError err;
|
|
int listen_fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (listen_fd == -1)
|
|
err.SetError(errno, DNBError::POSIX);
|
|
|
|
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
|
|
err.LogThreaded("::socket ( domain = AF_INET, type = SOCK_STREAM, protocol "
|
|
"= IPPROTO_TCP ) => socket = %i",
|
|
listen_fd);
|
|
|
|
if (err.Fail())
|
|
return rnb_err;
|
|
|
|
// enable local address reuse
|
|
SetSocketOption(listen_fd, SOL_SOCKET, SO_REUSEADDR, 1);
|
|
|
|
struct sockaddr_in sa;
|
|
::memset(&sa, 0, sizeof sa);
|
|
sa.sin_len = sizeof sa;
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(port);
|
|
sa.sin_addr.s_addr = INADDR_ANY; // Let incoming connections bind to any host
|
|
// network interface (this is NOT who can
|
|
// connect to us)
|
|
int error = ::bind(listen_fd, (struct sockaddr *)&sa, sizeof(sa));
|
|
if (error == -1)
|
|
err.SetError(errno, DNBError::POSIX);
|
|
|
|
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
|
|
err.LogThreaded(
|
|
"::bind ( socket = %i, (struct sockaddr *) &sa, sizeof(sa)) )",
|
|
listen_fd);
|
|
|
|
if (err.Fail()) {
|
|
ClosePort(listen_fd, false);
|
|
return rnb_err;
|
|
}
|
|
|
|
error = ::listen(listen_fd, 5);
|
|
if (error == -1)
|
|
err.SetError(errno, DNBError::POSIX);
|
|
|
|
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
|
|
err.LogThreaded("::listen ( socket = %i, backlog = 1 )", listen_fd);
|
|
|
|
if (err.Fail()) {
|
|
ClosePort(listen_fd, false);
|
|
return rnb_err;
|
|
}
|
|
|
|
if (callback) {
|
|
// 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".
|
|
if (port == 0) {
|
|
socklen_t sa_len = sizeof(sa);
|
|
if (getsockname(listen_fd, (struct sockaddr *)&sa, &sa_len) == 0) {
|
|
port = ntohs(sa.sin_port);
|
|
callback(callback_baton, port);
|
|
}
|
|
} else {
|
|
callback(callback_baton, port);
|
|
}
|
|
}
|
|
|
|
struct sockaddr_in accept_addr;
|
|
::memset(&accept_addr, 0, sizeof accept_addr);
|
|
accept_addr.sin_len = sizeof accept_addr;
|
|
|
|
bool accept_connection = false;
|
|
|
|
// Loop until we are happy with our connection
|
|
while (!accept_connection) {
|
|
socklen_t accept_addr_len = sizeof accept_addr;
|
|
m_fd =
|
|
::accept(listen_fd, (struct sockaddr *)&accept_addr, &accept_addr_len);
|
|
|
|
if (m_fd == -1)
|
|
err.SetError(errno, DNBError::POSIX);
|
|
|
|
if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
|
|
err.LogThreaded(
|
|
"::accept ( socket = %i, address = %p, address_len = %u )", listen_fd,
|
|
&accept_addr, accept_addr_len);
|
|
|
|
if (err.Fail())
|
|
break;
|
|
|
|
if (listen_addr.sin_addr.s_addr == INADDR_ANY)
|
|
accept_connection = true;
|
|
else {
|
|
if (accept_addr_len == listen_addr.sin_len &&
|
|
accept_addr.sin_addr.s_addr == listen_addr.sin_addr.s_addr) {
|
|
accept_connection = true;
|
|
} else {
|
|
::close(m_fd);
|
|
m_fd = -1;
|
|
const uint8_t *accept_ip =
|
|
(const uint8_t *)&accept_addr.sin_addr.s_addr;
|
|
const uint8_t *listen_ip =
|
|
(const uint8_t *)&listen_addr.sin_addr.s_addr;
|
|
::fprintf(stderr, "error: rejecting incoming connection from "
|
|
"%u.%u.%u.%u (expecting %u.%u.%u.%u)\n",
|
|
accept_ip[0], accept_ip[1], accept_ip[2], accept_ip[3],
|
|
listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3]);
|
|
DNBLogThreaded("error: rejecting connection from %u.%u.%u.%u "
|
|
"(expecting %u.%u.%u.%u)",
|
|
accept_ip[0], accept_ip[1], accept_ip[2], accept_ip[3],
|
|
listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ClosePort(listen_fd, false);
|
|
|
|
if (err.Fail()) {
|
|
return rnb_err;
|
|
} else {
|
|
// 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) {
|
|
Disconnect(false);
|
|
|
|
// Create the socket
|
|
m_fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (m_fd == -1)
|
|
return rnb_err;
|
|
|
|
// Enable local address reuse
|
|
SetSocketOption(m_fd, SOL_SOCKET, SO_REUSEADDR, 1);
|
|
|
|
struct sockaddr_in sa;
|
|
::memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(port);
|
|
|
|
if (!ResolveIPV4HostName(host, sa.sin_addr.s_addr)) {
|
|
DNBLogThreaded("error: failed to resolve host '%s'", host);
|
|
Disconnect(false);
|
|
return rnb_err;
|
|
}
|
|
|
|
if (-1 == ::connect(m_fd, (const struct sockaddr *)&sa, sizeof(sa))) {
|
|
Disconnect(false);
|
|
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::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;
|
|
}
|