mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
468 lines
16 KiB
C++
468 lines
16 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 <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);
|
|
}
|