You've already forked linux-packaging-mono
acceptance-tests
data
debian
docs
external
Newtonsoft.Json
api-doc-tools
api-snapshot
aspnetwebstack
bdwgc
binary-reference-assemblies
bockbuild
boringssl
cecil
cecil-legacy
corefx
corert
helix-binaries
ikdasm
ikvm
illinker-test-assets
linker
llvm-project
clang
clang-tools-extra
compiler-rt
libcxx
libcxxabi
libunwind
lld
lldb
cmake
docs
examples
include
lit
lldb.xcodeproj
lldb.xcworkspace
packages
resources
scripts
source
third_party
tools
argdumper
compact-unwind
darwin-debug
darwin-threads
debugserver
debugserver.xcodeproj
resources
scripts
source
MacOSX
ARM_DWARF_Registers.h
ARM_ehframe_Registers.h
CMakeLists.txt
ChangeLog
DNB.cpp
DNB.h
DNBArch.cpp
DNBArch.h
DNBBreakpoint.cpp
DNBBreakpoint.h
DNBDataRef.cpp
DNBDataRef.h
DNBDefs.h
DNBError.cpp
DNBError.h
DNBLog.cpp
DNBLog.h
DNBRegisterInfo.cpp
DNBRegisterInfo.h
DNBRuntimeAction.h
DNBThreadResumeActions.cpp
DNBThreadResumeActions.h
DNBTimer.h
JSON.cpp
JSON.h
JSONGenerator.h
PThreadCondition.h
PThreadEvent.cpp
PThreadEvent.h
PThreadMutex.cpp
PThreadMutex.h
PseudoTerminal.cpp
PseudoTerminal.h
RNBContext.cpp
RNBContext.h
RNBDefs.h
RNBRemote.cpp.REMOVED.git-id
RNBRemote.h
RNBServices.cpp
RNBServices.h
RNBSocket.cpp
RNBSocket.h
StdStringExtractor.cpp
StdStringExtractor.h
SysSignal.cpp
SysSignal.h
TTYState.cpp
TTYState.h
com.apple.debugserver.applist.internal.plist
com.apple.debugserver.applist.plist
com.apple.debugserver.internal.plist
com.apple.debugserver.plist
com.apple.debugserver.posix.plist
debugserver-entitlements.plist
debugserver-macosx-entitlements.plist
debugserver.cpp
libdebugserver.cpp
libdebugserver.h
CMakeLists.txt
debugnub-exports
driver
install-headers
intel-features
lldb-mi
lldb-perf
lldb-server
lldb-test
CMakeLists.txt
unittests
utils
www
.arcconfig
.clang-format
.gitignore
CMakeLists.txt
CODE_OWNERS.txt
INSTALL.txt
LICENSE.TXT
use_lldb_suite_root.py
llvm
openmp
polly
nuget-buildtasks
nunit-lite
roslyn-binaries
rx
xunit-binaries
how-to-bump-roslyn-binaries.md
ikvm-native
llvm
m4
man
mcs
mk
mono
msvc
netcore
po
runtime
samples
scripts
support
tools
COPYING.LIB
LICENSE
Makefile.am
Makefile.in
NEWS
README.md
acinclude.m4
aclocal.m4
autogen.sh
code_of_conduct.md
compile
config.guess
config.h.in
config.rpath
config.sub
configure.REMOVED.git-id
configure.ac.REMOVED.git-id
depcomp
install-sh
ltmain.sh.REMOVED.git-id
missing
mkinstalldirs
mono-uninstalled.pc.in
test-driver
winconfig.h
393 lines
11 KiB
C++
393 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 <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;
|
|
}
|