| // 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 <signal.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <cstdio> |
| #include <iostream> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/at_exit.h" |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/linked_ptr.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/pickle.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner.h" |
| #include "base/threading/thread.h" |
| #include "tools/android/forwarder2/common.h" |
| #include "tools/android/forwarder2/daemon.h" |
| #include "tools/android/forwarder2/host_controller.h" |
| #include "tools/android/forwarder2/pipe_notifier.h" |
| #include "tools/android/forwarder2/socket.h" |
| #include "tools/android/forwarder2/util.h" |
| |
| namespace forwarder2 { |
| namespace { |
| |
| const char kLogFilePath[] = "/tmp/host_forwarder_log"; |
| const char kDaemonIdentifier[] = "chrome_host_forwarder_daemon"; |
| |
| const int kBufSize = 256; |
| |
| // Needs to be global to be able to be accessed from the signal handler. |
| PipeNotifier* g_notifier = NULL; |
| |
| // Lets the daemon fetch the exit notifier file descriptor. |
| int GetExitNotifierFD() { |
| DCHECK(g_notifier); |
| return g_notifier->receiver_fd(); |
| } |
| |
| void KillHandler(int signal_number) { |
| char buf[kBufSize]; |
| if (signal_number != SIGTERM && signal_number != SIGINT) { |
| snprintf(buf, sizeof(buf), "Ignoring unexpected signal %d.", signal_number); |
| SIGNAL_SAFE_LOG(WARNING, buf); |
| return; |
| } |
| snprintf(buf, sizeof(buf), "Received signal %d.", signal_number); |
| SIGNAL_SAFE_LOG(WARNING, buf); |
| static int s_kill_handler_count = 0; |
| CHECK(g_notifier); |
| // If for some reason the forwarder get stuck in any socket waiting forever, |
| // we can send a SIGKILL or SIGINT three times to force it die |
| // (non-nicely). This is useful when debugging. |
| ++s_kill_handler_count; |
| if (!g_notifier->Notify() || s_kill_handler_count > 2) |
| exit(1); |
| } |
| |
| // Manages HostController instances. There is one HostController instance for |
| // each connection being forwarded. Note that forwarding can happen with many |
| // devices (identified with a serial id). |
| class HostControllersManager { |
| public: |
| HostControllersManager() |
| : controllers_(new HostControllerMap()), |
| has_failed_(false), |
| weak_ptr_factory_(this) { |
| } |
| |
| ~HostControllersManager() { |
| if (!thread_.get()) |
| return; |
| // Delete the controllers on the thread they were created on. |
| thread_->task_runner()->DeleteSoon(FROM_HERE, controllers_.release()); |
| } |
| |
| void HandleRequest(const std::string& adb_path, |
| const std::string& device_serial, |
| int device_port, |
| int host_port, |
| scoped_ptr<Socket> client_socket) { |
| // Lazy initialize so that the CLI process doesn't get this thread created. |
| InitOnce(); |
| thread_->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&HostControllersManager::HandleRequestOnInternalThread, |
| base::Unretained(this), adb_path, device_serial, device_port, |
| host_port, base::Passed(&client_socket))); |
| } |
| |
| bool has_failed() const { return has_failed_; } |
| |
| private: |
| typedef base::hash_map< |
| std::string, linked_ptr<HostController> > HostControllerMap; |
| |
| static std::string MakeHostControllerMapKey(int adb_port, int device_port) { |
| return base::StringPrintf("%d:%d", adb_port, device_port); |
| } |
| |
| void InitOnce() { |
| if (thread_.get()) |
| return; |
| at_exit_manager_.reset(new base::AtExitManager()); |
| thread_.reset(new base::Thread("HostControllersManagerThread")); |
| thread_->Start(); |
| } |
| |
| // Invoked when a HostController instance reports an error (e.g. due to a |
| // device connectivity issue). Note that this could be called after the |
| // controller manager was destroyed which is why a weak pointer is used. |
| static void DeleteHostController( |
| const base::WeakPtr<HostControllersManager>& manager_ptr, |
| scoped_ptr<HostController> host_controller) { |
| HostController* const controller = host_controller.release(); |
| HostControllersManager* const manager = manager_ptr.get(); |
| if (!manager) { |
| // Note that |controller| is not leaked in this case since the host |
| // controllers manager owns the controllers. If the manager was deleted |
| // then all the controllers (including |controller|) were also deleted. |
| return; |
| } |
| DCHECK(manager->thread_->task_runner()->RunsTasksOnCurrentThread()); |
| // Note that this will delete |controller| which is owned by the map. |
| DeleteRefCountedValueInMap( |
| MakeHostControllerMapKey( |
| controller->adb_port(), controller->device_port()), |
| manager->controllers_.get()); |
| } |
| |
| void HandleRequestOnInternalThread(const std::string& adb_path, |
| const std::string& device_serial, |
| int device_port, |
| int host_port, |
| scoped_ptr<Socket> client_socket) { |
| const int adb_port = GetAdbPortForDevice(adb_path, device_serial); |
| if (adb_port < 0) { |
| SendMessage( |
| "ERROR: could not get adb port for device. You might need to add " |
| "'adb' to your PATH or provide the device serial id.", |
| client_socket.get()); |
| return; |
| } |
| if (device_port < 0) { |
| // Remove the previously created host controller. |
| const std::string controller_key = MakeHostControllerMapKey( |
| adb_port, -device_port); |
| const bool controller_did_exist = DeleteRefCountedValueInMap( |
| controller_key, controllers_.get()); |
| SendMessage( |
| !controller_did_exist ? "ERROR: could not unmap port" : "OK", |
| client_socket.get()); |
| |
| RemoveAdbPortForDeviceIfNeeded(adb_path, device_serial); |
| return; |
| } |
| if (host_port < 0) { |
| SendMessage("ERROR: missing host port", client_socket.get()); |
| return; |
| } |
| const bool use_dynamic_port_allocation = device_port == 0; |
| if (!use_dynamic_port_allocation) { |
| const std::string controller_key = MakeHostControllerMapKey( |
| adb_port, device_port); |
| if (controllers_->find(controller_key) != controllers_->end()) { |
| LOG(INFO) << "Already forwarding device port " << device_port |
| << " to host port " << host_port; |
| SendMessage(base::StringPrintf("%d:%d", device_port, host_port), |
| client_socket.get()); |
| return; |
| } |
| } |
| // Create a new host controller. |
| scoped_ptr<HostController> host_controller( |
| HostController::Create( |
| device_port, host_port, adb_port, GetExitNotifierFD(), |
| base::Bind(&HostControllersManager::DeleteHostController, |
| weak_ptr_factory_.GetWeakPtr()))); |
| if (!host_controller.get()) { |
| has_failed_ = true; |
| SendMessage("ERROR: Connection to device failed.", client_socket.get()); |
| return; |
| } |
| // Get the current allocated port. |
| device_port = host_controller->device_port(); |
| LOG(INFO) << "Forwarding device port " << device_port << " to host port " |
| << host_port; |
| const std::string msg = base::StringPrintf("%d:%d", device_port, host_port); |
| if (!SendMessage(msg, client_socket.get())) |
| return; |
| host_controller->Start(); |
| controllers_->insert( |
| std::make_pair(MakeHostControllerMapKey(adb_port, device_port), |
| linked_ptr<HostController>(host_controller.release()))); |
| } |
| |
| void RemoveAdbPortForDeviceIfNeeded(const std::string& adb_path, |
| const std::string& device_serial) { |
| base::hash_map<std::string, int>::const_iterator it = |
| device_serial_to_adb_port_map_.find(device_serial); |
| if (it == device_serial_to_adb_port_map_.end()) |
| return; |
| |
| int port = it->second; |
| const std::string prefix = base::StringPrintf("%d:", port); |
| for (HostControllerMap::const_iterator others = controllers_->begin(); |
| others != controllers_->end(); ++others) { |
| if (others->first.find(prefix) == 0U) |
| return; |
| } |
| // No other port is being forwarded to this device: |
| // - Remove it from our internal serial -> adb port map. |
| // - Remove from "adb forward" command. |
| LOG(INFO) << "Device " << device_serial << " has no more ports."; |
| device_serial_to_adb_port_map_.erase(device_serial); |
| const std::string serial_part = device_serial.empty() ? |
| std::string() : std::string("-s ") + device_serial; |
| const std::string command = base::StringPrintf( |
| "%s %s forward --remove tcp:%d", |
| adb_path.c_str(), |
| serial_part.c_str(), |
| port); |
| const int ret = system(command.c_str()); |
| LOG(INFO) << command << " ret: " << ret; |
| // Wait for the socket to be fully unmapped. |
| const std::string port_mapped_cmd = base::StringPrintf( |
| "lsof -nPi:%d", |
| port); |
| const int poll_interval_us = 500 * 1000; |
| int retries = 3; |
| while (retries) { |
| const int port_unmapped = system(port_mapped_cmd.c_str()); |
| LOG(INFO) << "Device " << device_serial << " port " << port << " unmap " |
| << port_unmapped; |
| if (port_unmapped) |
| break; |
| --retries; |
| usleep(poll_interval_us); |
| } |
| } |
| |
| int GetAdbPortForDevice(const std::string adb_path, |
| const std::string& device_serial) { |
| base::hash_map<std::string, int>::const_iterator it = |
| device_serial_to_adb_port_map_.find(device_serial); |
| if (it != device_serial_to_adb_port_map_.end()) |
| return it->second; |
| Socket bind_socket; |
| CHECK(bind_socket.BindTcp("127.0.0.1", 0)); |
| const int port = bind_socket.GetPort(); |
| bind_socket.Close(); |
| const std::string serial_part = device_serial.empty() ? |
| std::string() : std::string("-s ") + device_serial; |
| const std::string command = base::StringPrintf( |
| "%s %s forward tcp:%d localabstract:chrome_device_forwarder", |
| adb_path.c_str(), |
| serial_part.c_str(), |
| port); |
| LOG(INFO) << command; |
| const int ret = system(command.c_str()); |
| if (ret < 0 || !WIFEXITED(ret) || WEXITSTATUS(ret) != 0) |
| return -1; |
| device_serial_to_adb_port_map_[device_serial] = port; |
| return port; |
| } |
| |
| bool SendMessage(const std::string& msg, Socket* client_socket) { |
| bool result = client_socket->WriteString(msg); |
| DCHECK(result); |
| if (!result) |
| has_failed_ = true; |
| return result; |
| } |
| |
| base::hash_map<std::string, int> device_serial_to_adb_port_map_; |
| scoped_ptr<HostControllerMap> controllers_; |
| bool has_failed_; |
| scoped_ptr<base::AtExitManager> at_exit_manager_; // Needed by base::Thread. |
| scoped_ptr<base::Thread> thread_; |
| base::WeakPtrFactory<HostControllersManager> weak_ptr_factory_; |
| }; |
| |
| class ServerDelegate : public Daemon::ServerDelegate { |
| public: |
| ServerDelegate(const std::string& adb_path) |
| : adb_path_(adb_path), has_failed_(false) {} |
| |
| bool has_failed() const { |
| return has_failed_ || controllers_manager_.has_failed(); |
| } |
| |
| // Daemon::ServerDelegate: |
| void Init() override { |
| LOG(INFO) << "Starting host process daemon (pid=" << getpid() << ")"; |
| DCHECK(!g_notifier); |
| g_notifier = new PipeNotifier(); |
| signal(SIGTERM, KillHandler); |
| signal(SIGINT, KillHandler); |
| } |
| |
| void OnClientConnected(scoped_ptr<Socket> client_socket) override { |
| char buf[kBufSize]; |
| const int bytes_read = client_socket->Read(buf, sizeof(buf)); |
| if (bytes_read <= 0) { |
| if (client_socket->DidReceiveEvent()) |
| return; |
| PError("Read()"); |
| has_failed_ = true; |
| return; |
| } |
| const base::Pickle command_pickle(buf, bytes_read); |
| base::PickleIterator pickle_it(command_pickle); |
| std::string device_serial; |
| CHECK(pickle_it.ReadString(&device_serial)); |
| int device_port; |
| if (!pickle_it.ReadInt(&device_port)) { |
| client_socket->WriteString("ERROR: missing device port"); |
| return; |
| } |
| int host_port; |
| if (!pickle_it.ReadInt(&host_port)) |
| host_port = -1; |
| controllers_manager_.HandleRequest(adb_path_, device_serial, device_port, |
| host_port, client_socket.Pass()); |
| } |
| |
| private: |
| std::string adb_path_; |
| bool has_failed_; |
| HostControllersManager controllers_manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServerDelegate); |
| }; |
| |
| class ClientDelegate : public Daemon::ClientDelegate { |
| public: |
| ClientDelegate(const base::Pickle& command_pickle) |
| : command_pickle_(command_pickle), has_failed_(false) {} |
| |
| bool has_failed() const { return has_failed_; } |
| |
| // Daemon::ClientDelegate: |
| void OnDaemonReady(Socket* daemon_socket) override { |
| // Send the forward command to the daemon. |
| CHECK_EQ(static_cast<long>(command_pickle_.size()), |
| daemon_socket->WriteNumBytes(command_pickle_.data(), |
| command_pickle_.size())); |
| char buf[kBufSize]; |
| const int bytes_read = daemon_socket->Read( |
| buf, sizeof(buf) - 1 /* leave space for null terminator */); |
| CHECK_GT(bytes_read, 0); |
| DCHECK(static_cast<size_t>(bytes_read) < sizeof(buf)); |
| buf[bytes_read] = 0; |
| base::StringPiece msg(buf, bytes_read); |
| if (msg.starts_with("ERROR")) { |
| LOG(ERROR) << msg; |
| has_failed_ = true; |
| return; |
| } |
| printf("%s\n", buf); |
| } |
| |
| private: |
| const base::Pickle command_pickle_; |
| bool has_failed_; |
| }; |
| |
| void ExitWithUsage() { |
| std::cerr << "Usage: host_forwarder [options]\n\n" |
| "Options:\n" |
| " --serial-id=[0-9A-Z]{16}]\n" |
| " --map DEVICE_PORT HOST_PORT\n" |
| " --unmap DEVICE_PORT\n" |
| " --adb PATH_TO_ADB\n" |
| " --kill-server\n"; |
| exit(1); |
| } |
| |
| int PortToInt(const std::string& s) { |
| int value; |
| // Note that 0 is a valid port (used for dynamic port allocation). |
| if (!base::StringToInt(s, &value) || value < 0 || |
| value > std::numeric_limits<uint16>::max()) { |
| LOG(ERROR) << "Could not convert string " << s << " to port"; |
| ExitWithUsage(); |
| } |
| return value; |
| } |
| |
| int RunHostForwarder(int argc, char** argv) { |
| base::CommandLine::Init(argc, argv); |
| const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess(); |
| std::string adb_path = "adb"; |
| bool kill_server = false; |
| |
| base::Pickle pickle; |
| pickle.WriteString( |
| cmd_line.HasSwitch("serial-id") ? |
| cmd_line.GetSwitchValueASCII("serial-id") : std::string()); |
| |
| const std::vector<std::string> args = cmd_line.GetArgs(); |
| if (cmd_line.HasSwitch("kill-server")) { |
| kill_server = true; |
| } else if (cmd_line.HasSwitch("unmap")) { |
| if (args.size() != 1) |
| ExitWithUsage(); |
| // Note the minus sign below. |
| pickle.WriteInt(-PortToInt(args[0])); |
| } else if (cmd_line.HasSwitch("map")) { |
| if (args.size() != 2) |
| ExitWithUsage(); |
| pickle.WriteInt(PortToInt(args[0])); |
| pickle.WriteInt(PortToInt(args[1])); |
| } else { |
| ExitWithUsage(); |
| } |
| |
| if (cmd_line.HasSwitch("adb")) { |
| adb_path = cmd_line.GetSwitchValueASCII("adb"); |
| } |
| |
| if (kill_server && args.size() > 0) |
| ExitWithUsage(); |
| |
| ClientDelegate client_delegate(pickle); |
| ServerDelegate daemon_delegate(adb_path); |
| Daemon daemon( |
| kLogFilePath, kDaemonIdentifier, &client_delegate, &daemon_delegate, |
| &GetExitNotifierFD); |
| |
| if (kill_server) |
| return !daemon.Kill(); |
| if (!daemon.SpawnIfNeeded()) |
| return 1; |
| |
| return client_delegate.has_failed() || daemon_delegate.has_failed(); |
| } |
| |
| } // namespace |
| } // namespace forwarder2 |
| |
| int main(int argc, char** argv) { |
| return forwarder2::RunHostForwarder(argc, argv); |
| } |