blob: 3e4d5604ad2efb49a7f54173e3a11a4b9d502ba1 [file] [log] [blame]
// Copyright 2019 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 "patchpanel/crostini_service.h"
#include <memory>
#include <utility>
#include <base/check.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "base/threading/thread_task_runner_handle.h"
#include <chromeos/constants/vm_tools.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/message.h>
#include <dbus/object_path.h>
#include <dbus/object_proxy.h>
#include "patchpanel/adb_proxy.h"
#include "patchpanel/guest_type.h"
namespace patchpanel {
namespace {
constexpr int32_t kInvalidID = 0;
constexpr int kDbusTimeoutMs = 200;
// The maximum number of ADB sideloading query failures before stopping.
constexpr int kAdbSideloadMaxTry = 5;
constexpr base::TimeDelta kAdbSideloadUpdateDelay =
base::TimeDelta::FromMilliseconds(5000);
std::string MakeKey(uint64_t vm_id, bool is_termina) {
return base::StringPrintf("%s:%s", is_termina ? "t" : "p",
base::NumberToString(vm_id).c_str());
}
} // namespace
CrostiniService::CrostiniService(
AddressManager* addr_mgr,
Datapath* datapath,
Device::ChangeEventHandler device_changed_handler)
: addr_mgr_(addr_mgr),
datapath_(datapath),
device_changed_handler_(device_changed_handler),
adb_sideloading_enabled_(false) {
DCHECK(addr_mgr_);
DCHECK(datapath_);
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = new dbus::Bus(options);
if (!bus_->Connect()) {
LOG(ERROR) << "Failed to connect to system bus";
} else {
CheckAdbSideloadingStatus();
}
}
CrostiniService::~CrostiniService() {
if (bus_)
bus_->ShutdownAndBlock();
}
bool CrostiniService::Start(uint64_t vm_id, bool is_termina, int subnet_index) {
if (vm_id == kInvalidID) {
LOG(ERROR) << "Invalid VM id";
return false;
}
const auto key = MakeKey(vm_id, is_termina);
if (taps_.find(key) != taps_.end()) {
LOG(WARNING) << "Already started for {id: " << vm_id << "}";
return false;
}
auto tap = AddTAP(is_termina, subnet_index);
if (!tap) {
LOG(ERROR) << "Cannot start for {id: " << vm_id << "}";
return false;
}
LOG(INFO) << "Crostini network service started for {id: " << vm_id << "}";
auto source = is_termina ? TrafficSource::CROSVM : TrafficSource::PLUGINVM;
datapath_->StartRoutingDevice("", tap->host_ifname(),
tap->config().host_ipv4_addr(), source,
true /*route_on_vpn*/);
if (adb_sideloading_enabled_)
StartAdbPortForwarding(tap->phys_ifname());
device_changed_handler_.Run(
*tap, Device::ChangeEvent::ADDED,
is_termina ? GuestMessage::TERMINA_VM : GuestMessage::PLUGIN_VM);
taps_.emplace(key, std::move(tap));
return true;
}
void CrostiniService::Stop(uint64_t vm_id, bool is_termina) {
const auto key = MakeKey(vm_id, is_termina);
const auto it = taps_.find(key);
if (it == taps_.end()) {
LOG(WARNING) << "Unknown {id: " << vm_id << "}";
return;
}
device_changed_handler_.Run(
*it->second, Device::ChangeEvent::REMOVED,
is_termina ? GuestMessage::TERMINA_VM : GuestMessage::PLUGIN_VM);
const auto& ifname = it->second->host_ifname();
auto source = is_termina ? TrafficSource::CROSVM : TrafficSource::PLUGINVM;
datapath_->StopRoutingDevice("", ifname,
it->second->config().host_ipv4_addr(), source,
true /*route_on_vpn*/);
if (adb_sideloading_enabled_)
StopAdbPortForwarding(ifname);
datapath_->RemoveInterface(ifname);
taps_.erase(key);
LOG(INFO) << "Crostini network service stopped for {id: " << vm_id << "}";
}
const Device* const CrostiniService::TAP(uint64_t vm_id,
bool is_termina) const {
const auto it = taps_.find(MakeKey(vm_id, is_termina));
if (it == taps_.end()) {
return nullptr;
}
return it->second.get();
}
std::vector<const Device*> CrostiniService::GetDevices() const {
std::vector<const Device*> devices;
for (const auto& [_, dev] : taps_) {
devices.push_back(dev.get());
}
return devices;
}
std::unique_ptr<Device> CrostiniService::AddTAP(bool is_termina,
int subnet_index) {
auto guest_type = is_termina ? GuestType::VM_TERMINA : GuestType::VM_PLUGIN;
auto ipv4_subnet = addr_mgr_->AllocateIPv4Subnet(guest_type, subnet_index);
if (!ipv4_subnet) {
LOG(ERROR) << "Subnet already in use or unavailable.";
return nullptr;
}
auto host_ipv4_addr = ipv4_subnet->AllocateAtOffset(0);
if (!host_ipv4_addr) {
LOG(ERROR) << "Host address already in use or unavailable.";
return nullptr;
}
auto guest_ipv4_addr = ipv4_subnet->AllocateAtOffset(1);
if (!guest_ipv4_addr) {
LOG(ERROR) << "VM address already in use or unavailable.";
return nullptr;
}
std::unique_ptr<Subnet> lxd_subnet;
if (is_termina) {
lxd_subnet = addr_mgr_->AllocateIPv4Subnet(GuestType::LXD_CONTAINER);
if (!lxd_subnet) {
LOG(ERROR) << "lxd subnet already in use or unavailable.";
return nullptr;
}
}
const auto mac_addr = addr_mgr_->GenerateMacAddress(subnet_index);
const std::string tap =
datapath_->AddTAP("" /* auto-generate name */, &mac_addr,
host_ipv4_addr.get(), vm_tools::kCrosVmUser);
if (tap.empty()) {
LOG(ERROR) << "Failed to create TAP device.";
return nullptr;
}
if (lxd_subnet) {
// Setup lxd route for the container using the VM as a gateway.
if (!datapath_->AddIPv4Route(ipv4_subnet->AddressAtOffset(1),
lxd_subnet->AddressAtOffset(0),
lxd_subnet->Netmask())) {
LOG(ERROR) << "Failed to setup lxd route";
return nullptr;
}
}
auto config = std::make_unique<Device::Config>(
mac_addr, std::move(ipv4_subnet), std::move(host_ipv4_addr),
std::move(guest_ipv4_addr), std::move(lxd_subnet));
return std::make_unique<Device>(guest_type, tap, tap, "", std::move(config));
}
void CrostiniService::StartAdbPortForwarding(const std::string& ifname) {
if (!datapath_->AddAdbPortForwardRule(ifname)) {
LOG(ERROR) << "Error adding ADB port forwarding rule for " << ifname;
return;
}
if (!datapath_->AddAdbPortAccessRule(ifname)) {
datapath_->DeleteAdbPortForwardRule(ifname);
LOG(ERROR) << "Error adding ADB port access rule for " << ifname;
return;
}
if (!datapath_->SetRouteLocalnet(ifname, true)) {
LOG(ERROR) << "Failed to set up route localnet for " << ifname;
return;
}
}
void CrostiniService::StopAdbPortForwarding(const std::string& ifname) {
datapath_->DeleteAdbPortForwardRule(ifname);
datapath_->DeleteAdbPortAccessRule(ifname);
datapath_->SetRouteLocalnet(ifname, false);
}
void CrostiniService::CheckAdbSideloadingStatus() {
static int num_try = 0;
if (num_try >= kAdbSideloadMaxTry) {
LOG(WARNING) << "Failed to get ADB sideloading status after " << num_try
<< " tries. ADB sideloading will not work";
return;
}
dbus::ObjectProxy* proxy = bus_->GetObjectProxy(
login_manager::kSessionManagerServiceName,
dbus::ObjectPath(login_manager::kSessionManagerServicePath));
dbus::MethodCall method_call(login_manager::kSessionManagerInterface,
login_manager::kSessionManagerQueryAdbSideload);
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
if (!dbus_response) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CrostiniService::CheckAdbSideloadingStatus,
weak_factory_.GetWeakPtr()),
kAdbSideloadUpdateDelay);
num_try++;
return;
}
dbus::MessageReader reader(dbus_response.get());
reader.PopBool(&adb_sideloading_enabled_);
if (!adb_sideloading_enabled_)
return;
// If ADB sideloading is enabled, start ADB forwarding on all configured
// Crostini's TAP interfaces.
for (const auto& tap : taps_) {
StartAdbPortForwarding(tap.second->phys_ifname());
}
}
} // namespace patchpanel