blob: 919e0d745913974b60b0efb7df02b3732e97a96d [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "vm_tools/garcon/arc_sideload.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <utility>
#include <vector>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "vm_tools/garcon/package_kit_proxy.h"
namespace vm_tools {
namespace garcon {
namespace {
constexpr char kDeviceName[] = "eth0";
constexpr char kBridgePort[] = "5555";
constexpr char kBridgeHost[] = "arc";
// Wrapper to store the exit code and output of running a command.
using CommandOutput = std::pair<int, std::string>;
// Run the command given in |argv| (including all its flags). Returns its exit
// code and output.
CommandOutput RunCommand(base::CommandLine::StringVector argv) {
CommandOutput ret{0, ""};
// TODO(crbug.com/1047543): For some reason this call does not work as
// documented. It always returns false (i.e. the app does not exit cleanly)
// even when the output of the command is correct and what we expect. Until we
// fix it we will be unable to detect when the adb sideload configuration
// fails.
base::GetAppOutputAndError(base::CommandLine{argv}, &ret.second);
ret.first = 0;
return ret;
}
// Determine the ipv4 address of the system and write it into out_result.
// Returns true on success, false otherwise. If we do not succeed, a useful
// error message will be written into out_result.
bool GetIpv4Address(std::string* out_result) {
struct ifaddrs* head;
if (getifaddrs(&head) != 0) {
*out_result = std::string("getifaddrs failed: ") + strerror(errno);
return false;
}
bool success = false;
*out_result = std::string("Failed to find device ") + kDeviceName;
for (struct ifaddrs* current = head; current != nullptr;
current = current->ifa_next) {
if (current->ifa_addr->sa_family != AF_INET)
continue;
if (std::strcmp(current->ifa_name, kDeviceName) != 0)
continue;
char host_info[NI_MAXHOST];
if (getnameinfo(current->ifa_addr, sizeof(struct sockaddr_in), host_info,
NI_MAXHOST, NULL, 0, NI_NUMERICHOST) != 0) {
*out_result = std::string("Failed to get name for device ") + kDeviceName;
} else {
*out_result = std::string(host_info);
success = true;
}
break;
}
freeifaddrs(head);
return success;
}
// Determine the address of the adb device and write it into out_result.
// Returns true on success, false otherwise. If we do not succeed, a useful
// error message will be written into out_result.
bool GetArcBridgeAddress(std::string* out_result) {
struct addrinfo* head;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int retcode = getaddrinfo(kBridgeHost, nullptr, &hints, &head);
if (retcode != 0) {
*out_result = std::string("getaddrinfo failed: ") + gai_strerror(retcode);
return false;
}
bool success = false;
if (head->ai_next != nullptr) {
*out_result = std::string("Multiple addresses found for ") + kBridgeHost;
} else {
char addrstr[100];
struct sockaddr_in* ipv4_addr = (struct sockaddr_in*)head->ai_addr;
struct in_addr* ptr = &ipv4_addr->sin_addr;
if (!inet_ntop(head->ai_family, ptr, addrstr, sizeof(addrstr))) {
*out_result = std::string("inet_ntop failed: ") + strerror(errno);
} else {
*out_result = std::string(addrstr);
success = true;
}
}
freeaddrinfo(head);
return success;
}
} // namespace
bool ArcSideload::Enable(std::string* out_error) {
// This only needs to be done once per-session, so we short circuit in the
// event that another instance succeeded.
if (enable_completed_successfully_this_session_)
return true;
std::string ip_address;
if (!GetIpv4Address(&ip_address)) {
*out_error = "Failed to determine ipv4 address: " + ip_address;
return false;
}
std::string bridge_ip;
if (!GetArcBridgeAddress(&bridge_ip)) {
*out_error = "Failed to determine bridge address: " + bridge_ip;
return false;
}
// Configure the kernel to allow routing.
CommandOutput sysctl_cmd = RunCommand(
{"sudo", "--", "sysctl", "-w", "net.ipv4.conf.eth0.route_localnet=1"});
if (sysctl_cmd.first != 0) {
*out_error = "Failed to configure eth0 for routing: " + sysctl_cmd.second;
return false;
}
// Set up the routing rules.
CommandOutput ipt_out_cmd =
RunCommand({"sudo", "--", "iptables", "-t", "nat", "-A", "OUTPUT", "-p",
"tcp", "-d", "127.0.0.1", "--dport", "5555", "-j", "DNAT",
"--to", bridge_ip + ":" + kBridgePort});
if (ipt_out_cmd.first != 0) {
*out_error = "Failed to configure iptables to output to the bridge: " +
ipt_out_cmd.second;
return false;
}
CommandOutput ipt_route_cmd =
RunCommand({"sudo", "--", "iptables", "-t", "nat", "-A", "POSTROUTING",
"-p", "tcp", "-s", "127.0.0.1", "-d", bridge_ip, "--dport",
kBridgePort, "-j", "SNAT", "--to", std::string(ip_address)});
if (ipt_route_cmd.first != 0) {
*out_error = "Failed to configure iptables to route to the container: " +
ipt_route_cmd.second;
return false;
}
LOG(INFO) << "Enabled arc sideloading";
enable_completed_successfully_this_session_ = true;
return true;
}
bool ArcSideload::enable_completed_successfully_this_session_ = false;
} // namespace garcon
} // namespace vm_tools