Index: tools/android/forwarder/forwarder.cc |
diff --git a/tools/android/forwarder/forwarder.cc b/tools/android/forwarder/forwarder.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1d24ad2c5cb4932caf3f69dd025770d06bf6c9dc |
--- /dev/null |
+++ b/tools/android/forwarder/forwarder.cc |
@@ -0,0 +1,426 @@ |
+// 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; |
+ 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"); |
+ |
+ base::CommandLine command_line(argc, argv); |
+ base::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; |
+} |
+ |