| // 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; |
| } |
| |