427 lines
11 KiB
C++
427 lines
11 KiB
C++
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/logging.h"
|
|
#include "base/posix/eintr_wrapper.h"
|
|
#include "tools/android/common/adb_connection.h"
|
|
#include "tools/android/common/daemon.h"
|
|
#include "tools/android/common/net.h"
|
|
|
|
namespace {
|
|
|
|
const pthread_t kInvalidThread = static_cast<pthread_t>(-1);
|
|
volatile bool g_killed = false;
|
|
|
|
void CloseSocket(int fd) {
|
|
if (fd >= 0) {
|
|
int old_errno = errno;
|
|
(void) HANDLE_EINTR(close(fd));
|
|
errno = old_errno;
|
|
}
|
|
}
|
|
|
|
class Buffer {
|
|
public:
|
|
Buffer()
|
|
: bytes_read_(0),
|
|
write_offset_(0) {
|
|
}
|
|
|
|
bool CanRead() {
|
|
return bytes_read_ == 0;
|
|
}
|
|
|
|
bool CanWrite() {
|
|
return write_offset_ < bytes_read_;
|
|
}
|
|
|
|
int Read(int fd) {
|
|
int ret = -1;
|
|
if (CanRead()) {
|
|
ret = HANDLE_EINTR(read(fd, buffer_, kBufferSize));
|
|
if (ret > 0)
|
|
bytes_read_ = ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int Write(int fd) {
|
|
int ret = -1;
|
|
if (CanWrite()) {
|
|
ret = HANDLE_EINTR(write(fd, buffer_ + write_offset_,
|
|
bytes_read_ - write_offset_));
|
|
if (ret > 0) {
|
|
write_offset_ += ret;
|
|
if (write_offset_ == bytes_read_) {
|
|
write_offset_ = 0;
|
|
bytes_read_ = 0;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
// A big buffer to let our file-over-http bridge work more like real file.
|
|
static const int kBufferSize = 1024 * 128;
|
|
int bytes_read_;
|
|
int write_offset_;
|
|
char buffer_[kBufferSize];
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(Buffer);
|
|
};
|
|
|
|
class Server;
|
|
|
|
struct ForwarderThreadInfo {
|
|
ForwarderThreadInfo(Server* a_server, int a_forwarder_index)
|
|
: server(a_server),
|
|
forwarder_index(a_forwarder_index) {
|
|
}
|
|
Server* server;
|
|
int forwarder_index;
|
|
};
|
|
|
|
struct ForwarderInfo {
|
|
time_t start_time;
|
|
int socket1;
|
|
time_t socket1_last_byte_time;
|
|
size_t socket1_bytes;
|
|
int socket2;
|
|
time_t socket2_last_byte_time;
|
|
size_t socket2_bytes;
|
|
};
|
|
|
|
class Server {
|
|
public:
|
|
Server()
|
|
: thread_(kInvalidThread),
|
|
socket_(-1) {
|
|
memset(forward_to_, 0, sizeof(forward_to_));
|
|
memset(&forwarders_, 0, sizeof(forwarders_));
|
|
}
|
|
|
|
int GetFreeForwarderIndex() {
|
|
for (int i = 0; i < kMaxForwarders; i++) {
|
|
if (forwarders_[i].start_time == 0)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void DisposeForwarderInfo(int index) {
|
|
forwarders_[index].start_time = 0;
|
|
}
|
|
|
|
ForwarderInfo* GetForwarderInfo(int index) {
|
|
return &forwarders_[index];
|
|
}
|
|
|
|
void DumpInformation() {
|
|
LOG(INFO) << "Server information: " << forward_to_;
|
|
LOG(INFO) << "No.: age up(bytes,idle) down(bytes,idle)";
|
|
int count = 0;
|
|
time_t now = time(NULL);
|
|
for (int i = 0; i < kMaxForwarders; i++) {
|
|
const ForwarderInfo& info = forwarders_[i];
|
|
if (info.start_time) {
|
|
count++;
|
|
LOG(INFO) << count << ": " << now - info.start_time << " up("
|
|
<< info.socket1_bytes << ","
|
|
<< now - info.socket1_last_byte_time << " down("
|
|
<< info.socket2_bytes << ","
|
|
<< now - info.socket2_last_byte_time << ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shutdown() {
|
|
if (socket_ >= 0)
|
|
shutdown(socket_, SHUT_RDWR);
|
|
}
|
|
|
|
bool InitSocket(const char* arg);
|
|
|
|
void StartThread() {
|
|
pthread_create(&thread_, NULL, ServerThread, this);
|
|
}
|
|
|
|
void JoinThread() {
|
|
if (thread_ != kInvalidThread)
|
|
pthread_join(thread_, NULL);
|
|
}
|
|
|
|
private:
|
|
static void* ServerThread(void* arg);
|
|
|
|
// There are 3 kinds of threads that will access the array:
|
|
// 1. Server thread will get a free ForwarderInfo and initialize it;
|
|
// 2. Forwarder threads will dispose the ForwarderInfo when it finishes;
|
|
// 3. Main thread will iterate and print the forwarders.
|
|
// Using an array is not optimal, but can avoid locks or other complex
|
|
// inter-thread communication.
|
|
static const int kMaxForwarders = 512;
|
|
ForwarderInfo forwarders_[kMaxForwarders];
|
|
|
|
pthread_t thread_;
|
|
int socket_;
|
|
char forward_to_[40];
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(Server);
|
|
};
|
|
|
|
// Forwards all outputs from one socket to another socket.
|
|
void* ForwarderThread(void* arg) {
|
|
ForwarderThreadInfo* thread_info =
|
|
reinterpret_cast<ForwarderThreadInfo*>(arg);
|
|
Server* server = thread_info->server;
|
|
int index = thread_info->forwarder_index;
|
|
delete thread_info;
|
|
ForwarderInfo* info = server->GetForwarderInfo(index);
|
|
int socket1 = info->socket1;
|
|
int socket2 = info->socket2;
|
|
int nfds = socket1 > socket2 ? socket1 + 1 : socket2 + 1;
|
|
fd_set read_fds;
|
|
fd_set write_fds;
|
|
Buffer buffer1;
|
|
Buffer buffer2;
|
|
|
|
while (!g_killed) {
|
|
FD_ZERO(&read_fds);
|
|
if (buffer1.CanRead())
|
|
FD_SET(socket1, &read_fds);
|
|
if (buffer2.CanRead())
|
|
FD_SET(socket2, &read_fds);
|
|
|
|
FD_ZERO(&write_fds);
|
|
if (buffer1.CanWrite())
|
|
FD_SET(socket2, &write_fds);
|
|
if (buffer2.CanWrite())
|
|
FD_SET(socket1, &write_fds);
|
|
|
|
if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, NULL)) <= 0) {
|
|
LOG(ERROR) << "Select error: " << strerror(errno);
|
|
break;
|
|
}
|
|
|
|
int now = time(NULL);
|
|
if (FD_ISSET(socket1, &read_fds)) {
|
|
info->socket1_last_byte_time = now;
|
|
int bytes = buffer1.Read(socket1);
|
|
if (bytes <= 0)
|
|
break;
|
|
info->socket1_bytes += bytes;
|
|
}
|
|
if (FD_ISSET(socket2, &read_fds)) {
|
|
info->socket2_last_byte_time = now;
|
|
int bytes = buffer2.Read(socket2);
|
|
if (bytes <= 0)
|
|
break;
|
|
info->socket2_bytes += bytes;
|
|
}
|
|
if (FD_ISSET(socket1, &write_fds)) {
|
|
if (buffer2.Write(socket1) <= 0)
|
|
break;
|
|
}
|
|
if (FD_ISSET(socket2, &write_fds)) {
|
|
if (buffer1.Write(socket2) <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
CloseSocket(socket1);
|
|
CloseSocket(socket2);
|
|
server->DisposeForwarderInfo(index);
|
|
return NULL;
|
|
}
|
|
|
|
// Listens to a server socket. On incoming request, forward it to the host.
|
|
// static
|
|
void* Server::ServerThread(void* arg) {
|
|
Server* server = reinterpret_cast<Server*>(arg);
|
|
while (!g_killed) {
|
|
int forwarder_index = server->GetFreeForwarderIndex();
|
|
if (forwarder_index < 0) {
|
|
LOG(ERROR) << "Too many forwarders";
|
|
continue;
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
socklen_t addr_len = sizeof(addr);
|
|
int socket = HANDLE_EINTR(accept(server->socket_,
|
|
reinterpret_cast<sockaddr*>(&addr),
|
|
&addr_len));
|
|
if (socket < 0) {
|
|
LOG(ERROR) << "Failed to accept: " << strerror(errno);
|
|
break;
|
|
}
|
|
tools::DisableNagle(socket);
|
|
|
|
int host_socket = tools::ConnectAdbHostSocket(server->forward_to_);
|
|
if (host_socket >= 0) {
|
|
// Set NONBLOCK flag because we use select().
|
|
fcntl(socket, F_SETFL, fcntl(socket, F_GETFL) | O_NONBLOCK);
|
|
fcntl(host_socket, F_SETFL, fcntl(host_socket, F_GETFL) | O_NONBLOCK);
|
|
|
|
ForwarderInfo* forwarder_info = server->GetForwarderInfo(forwarder_index);
|
|
time_t now = time(NULL);
|
|
forwarder_info->start_time = now;
|
|
forwarder_info->socket1 = socket;
|
|
forwarder_info->socket1_last_byte_time = now;
|
|
forwarder_info->socket1_bytes = 0;
|
|
forwarder_info->socket2 = host_socket;
|
|
forwarder_info->socket2_last_byte_time = now;
|
|
forwarder_info->socket2_bytes = 0;
|
|
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, ForwarderThread,
|
|
new ForwarderThreadInfo(server, forwarder_index));
|
|
} else {
|
|
// Close the unused client socket which is failed to connect to host.
|
|
CloseSocket(socket);
|
|
}
|
|
}
|
|
|
|
CloseSocket(server->socket_);
|
|
server->socket_ = -1;
|
|
return NULL;
|
|
}
|
|
|
|
// Format of arg: <Device port>[:<Forward to port>:<Forward to address>]
|
|
bool Server::InitSocket(const char* arg) {
|
|
char* endptr;
|
|
int local_port = static_cast<int>(strtol(arg, &endptr, 10));
|
|
if (local_port < 0)
|
|
return false;
|
|
|
|
if (*endptr != ':') {
|
|
snprintf(forward_to_, sizeof(forward_to_), "%d:127.0.0.1", local_port);
|
|
} else {
|
|
strncpy(forward_to_, endptr + 1, sizeof(forward_to_) - 1);
|
|
}
|
|
|
|
socket_ = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (socket_ < 0) {
|
|
perror("server socket");
|
|
return false;
|
|
}
|
|
tools::DisableNagle(socket_);
|
|
|
|
sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
addr.sin_port = htons(local_port);
|
|
int reuse_addr = 1;
|
|
setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
|
|
&reuse_addr, sizeof(reuse_addr));
|
|
tools::DeferAccept(socket_);
|
|
if (HANDLE_EINTR(bind(socket_, reinterpret_cast<sockaddr*>(&addr),
|
|
sizeof(addr))) < 0 ||
|
|
HANDLE_EINTR(listen(socket_, 5)) < 0) {
|
|
perror("server bind");
|
|
CloseSocket(socket_);
|
|
socket_ = -1;
|
|
return false;
|
|
}
|
|
|
|
if (local_port == 0) {
|
|
socklen_t addrlen = sizeof(addr);
|
|
if (getsockname(socket_, reinterpret_cast<sockaddr*>(&addr), &addrlen)
|
|
!= 0) {
|
|
perror("get listen address");
|
|
CloseSocket(socket_);
|
|
socket_ = -1;
|
|
return false;
|
|
}
|
|
local_port = ntohs(addr.sin_port);
|
|
}
|
|
|
|
printf("Forwarding device port %d to host %s\n", local_port, forward_to_);
|
|
return true;
|
|
}
|
|
|
|
int g_server_count = 0;
|
|
Server* g_servers = NULL;
|
|
|
|
void KillHandler(int unused) {
|
|
g_killed = true;
|
|
for (int i = 0; i < g_server_count; i++)
|
|
g_servers[i].Shutdown();
|
|
}
|
|
|
|
void DumpInformation(int unused) {
|
|
for (int i = 0; i < g_server_count; i++)
|
|
g_servers[i].DumpInformation();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
printf("Android device to host TCP forwarder\n");
|
|
printf("Like 'adb forward' but in the reverse direction\n");
|
|
|
|
CommandLine command_line(argc, argv);
|
|
CommandLine::StringVector server_args = command_line.GetArgs();
|
|
if (tools::HasHelpSwitch(command_line) || server_args.empty()) {
|
|
tools::ShowHelp(
|
|
argv[0],
|
|
"<Device port>[:<Forward to port>:<Forward to address>] ...",
|
|
" <Forward to port> default is <Device port>\n"
|
|
" <Forward to address> default is 127.0.0.1\n"
|
|
"If <Device port> is 0, a port will by dynamically allocated.\n");
|
|
return 0;
|
|
}
|
|
|
|
g_servers = new Server[server_args.size()];
|
|
g_server_count = 0;
|
|
int failed_count = 0;
|
|
for (size_t i = 0; i < server_args.size(); i++) {
|
|
if (!g_servers[g_server_count].InitSocket(server_args[i].c_str())) {
|
|
printf("Couldn't start forwarder server for port spec: %s\n",
|
|
server_args[i].c_str());
|
|
++failed_count;
|
|
} else {
|
|
++g_server_count;
|
|
}
|
|
}
|
|
|
|
if (g_server_count == 0) {
|
|
printf("No forwarder servers could be started. Exiting.\n");
|
|
delete [] g_servers;
|
|
return failed_count;
|
|
}
|
|
|
|
if (!tools::HasNoSpawnDaemonSwitch(command_line))
|
|
tools::SpawnDaemon(failed_count);
|
|
|
|
signal(SIGTERM, KillHandler);
|
|
signal(SIGUSR2, DumpInformation);
|
|
|
|
for (int i = 0; i < g_server_count; i++)
|
|
g_servers[i].StartThread();
|
|
for (int i = 0; i < g_server_count; i++)
|
|
g_servers[i].JoinThread();
|
|
g_server_count = 0;
|
|
delete [] g_servers;
|
|
|
|
return 0;
|
|
}
|
|
|