blob: 448b565550ae8bb35e0e818b56dcdc46c4a7c156 [file] [log] [blame] [edit]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/arc_service.h"
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <optional>
#include <string_view>
#include <utility>
#include <base/containers/fixed_flat_set.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/strings/strcat.h>
#include <base/strings/string_number_conversions.h>
#include <base/system/sys_info.h>
#include <brillo/key_value_store.h>
#include <chromeos/constants/vm_tools.h>
#include <chromeos/net-base/ipv4_address.h>
#include <chromeos/net-base/mac_address.h>
#include <chromeos/net-base/technology.h>
#include <metrics/metrics_library.h>
#include <patchpanel/proto_bindings/patchpanel_service.pb.h>
#include "patchpanel/address_manager.h"
#include "patchpanel/datapath.h"
#include "patchpanel/metrics.h"
#include "patchpanel/proto_utils.h"
#include "patchpanel/scoped_ns.h"
#include "patchpanel/shill_client.h"
#include "patchpanel/vm_concierge_client.h"
namespace patchpanel {
namespace {
// UID of Android root, relative to the host pid namespace.
const int32_t kAndroidRootUid = 655360;
// Allocate 5 subnets for physical interfaces.
constexpr uint32_t kConfigPoolSize = 5;
constexpr uint32_t kInvalidId = 0;
constexpr char kArcNetnsName[] = "arc_netns";
constexpr char kArcVmIfnamePrefix[] = "eth";
void RecordEvent(MetricsLibraryInterface* metrics, ArcServiceUmaEvent event) {
metrics->SendEnumToUMA(kArcServiceUmaEventMetrics, event);
}
bool IsArcValidTechnology(std::optional<net_base::Technology> technology) {
// For now ignore WiFi Direct shill Networks until patchpanel is explicitly
// aware of Android's WiFi Direct client connection API calls via ARC.
static constexpr auto allowed_technologies =
base::MakeFixedFlatSet<net_base::Technology>({
net_base::Technology::kCellular,
net_base::Technology::kWiFi,
net_base::Technology::kEthernet,
});
return technology.has_value() &&
allowed_technologies.find(*technology) != allowed_technologies.end();
}
bool IsAdbAllowed(std::optional<net_base::Technology> technology) {
static constexpr auto allowed_technologies =
base::MakeFixedFlatSet<net_base::Technology>({
net_base::Technology::kEthernet,
net_base::Technology::kWiFi,
});
return technology.has_value() &&
allowed_technologies.find(*technology) != allowed_technologies.end();
}
bool KernelVersion(int* major, int* minor) {
struct utsname u;
if (uname(&u) != 0) {
PLOG(ERROR) << "uname failed";
*major = *minor = 0;
return false;
}
int unused;
if (sscanf(u.release, "%d.%d.%d", major, minor, &unused) != 3) {
LOG(ERROR) << "unexpected release string: " << u.release;
*major = *minor = 0;
return false;
}
return true;
}
// Makes Android root the owner of /sys/class/ + |path|. |pid| is the ARC
// container pid.
bool SetSysfsOwnerToAndroidRoot(pid_t pid, std::string_view path) {
auto ns = ScopedNS::EnterMountNS(pid);
if (!ns) {
LOG(ERROR) << "Cannot enter mnt namespace for pid " << pid;
return false;
}
const std::string sysfs_path = base::StrCat({"/sys/class/", path});
if (chown(sysfs_path.c_str(), kAndroidRootUid, kAndroidRootUid) == -1) {
PLOG(ERROR) << "Failed to change ownership of " + sysfs_path;
return false;
}
return true;
}
bool OneTimeContainerSetup(Datapath& datapath, pid_t pid) {
static bool done = false;
if (done)
return true;
bool success = true;
// Load networking modules needed by Android that are not compiled in the
// kernel. Android does not allow auto-loading of kernel modules.
// Expected for all kernels.
if (!datapath.ModprobeAll({
// The netfilter modules needed by netd for iptables commands.
"ip6table_filter",
"ip6t_ipv6header",
"ip6t_REJECT",
// The ipsec modules for AH and ESP encryption for ipv6.
"ah6",
"esp6",
})) {
LOG(ERROR) << "One or more required kernel modules failed to load."
<< " Some Android functionality may be broken.";
success = false;
}
// The xfrm modules needed for Android's ipsec APIs on kernels < 5.4.
int major, minor;
if (KernelVersion(&major, &minor) &&
(major < 5 || (major == 5 && minor < 4)) &&
!datapath.ModprobeAll({
"xfrm4_mode_transport",
"xfrm4_mode_tunnel",
"xfrm6_mode_transport",
"xfrm6_mode_tunnel",
})) {
LOG(ERROR) << "One or more required kernel modules failed to load."
<< " Some Android functionality may be broken.";
success = false;
}
// Additional modules optional for CTS compliance but required for some
// Android features.
if (!datapath.ModprobeAll({
// This module is not available in kernels < 3.18
"nf_reject_ipv6",
// These modules are needed for supporting Chrome traffic on Android
// VPN which uses Android's NAT feature. Android NAT sets up
// iptables
// rules that use these conntrack modules for FTP/TFTP.
"nf_nat_ftp",
"nf_nat_tftp",
// The tun module is needed by the Android 464xlat clatd process.
"tun",
})) {
LOG(WARNING) << "One or more optional kernel modules failed to load.";
success = false;
}
// This is only needed for CTS (b/27932574).
if (!SetSysfsOwnerToAndroidRoot(pid, "xt_idletimer")) {
success = false;
}
done = true;
return success;
}
std::string PrefixIfname(std::string_view prefix, std::string_view ifname) {
std::string n;
n.append(prefix);
n.append(ifname);
if (n.length() >= IFNAMSIZ) {
n.resize(IFNAMSIZ - 1);
// Best effort attempt to preserve the interface number, assuming it's the
// last char in the name.
n.back() = ifname.back();
}
return n;
}
} // namespace
bool ArcService::IsVM(ArcType type) {
switch (type) {
case ArcType::kContainer:
return false;
case ArcType::kVMHotplug:
return true;
case ArcType::kVMStatic:
return true;
}
}
ArcService::ArcConfig::ArcConfig(const net_base::MacAddress& mac_addr,
std::unique_ptr<Subnet> ipv4_subnet)
: mac_addr_(mac_addr), ipv4_subnet_(std::move(ipv4_subnet)) {}
ArcService::ArcDevice::ArcDevice(
ArcType type,
std::optional<net_base::Technology> technology,
std::optional<std::string_view> shill_device_ifname,
std::string_view arc_device_ifname,
net_base::MacAddress arc_device_mac_address,
const ArcConfig& arc_config,
std::string_view bridge_ifname,
std::string_view guest_device_ifname)
: type_(type),
technology_(technology),
shill_device_ifname_(shill_device_ifname),
arc_device_ifname_(arc_device_ifname),
arc_device_mac_address_(arc_device_mac_address),
arc_ipv4_subnet_(arc_config.ipv4_subnet()),
arc_ipv4_address_(arc_config.arc_ipv4_address()),
bridge_ipv4_address_(arc_config.bridge_ipv4_address()),
bridge_ifname_(bridge_ifname),
guest_device_ifname_(guest_device_ifname) {}
ArcService::ArcDevice::~ArcDevice() = default;
void ArcService::ArcDevice::ConvertToProto(NetworkDevice* output) const {
// By convention, |phys_ifname| is set to "arc0" for the "arc0" device used
// for VPN forwarding.
output->set_phys_ifname(shill_device_ifname().value_or(kArc0Ifname));
output->set_ifname(bridge_ifname());
output->set_guest_ifname(guest_device_ifname());
output->set_ipv4_addr(arc_ipv4_address().address().ToInAddr().s_addr);
output->set_host_ipv4_addr(bridge_ipv4_address().address().ToInAddr().s_addr);
if (IsVM(type())) {
output->set_guest_type(NetworkDevice::ARCVM);
} else {
output->set_guest_type(NetworkDevice::ARC);
}
if (technology().has_value()) {
switch (technology().value()) {
case net_base::Technology::kCellular:
output->set_technology_type(NetworkDevice::CELLULAR);
break;
case net_base::Technology::kWiFi:
output->set_technology_type(NetworkDevice::WIFI);
break;
case net_base::Technology::kEthernet:
output->set_technology_type(NetworkDevice::ETHERNET);
break;
default:
break;
}
}
FillSubnetProto(arc_ipv4_subnet(), output->mutable_ipv4_subnet());
}
ArcService::StaticGuestIfManager::StaticGuestIfManager(
const std::vector<std::string>& host_ifnames) {
int eth_idx = 0;
// Inside ARCVM, interface names follow the pattern eth%d (starting from 0)
// following the order of the host tap interfaces.
for (const auto& host_ifname : host_ifnames) {
guest_if_names_.try_emplace(host_ifname,
kArcVmIfnamePrefix + std::to_string(eth_idx++));
}
}
std::optional<std::string> ArcService::StaticGuestIfManager::AddInterface(
std::string_view host_ifname) {
LOG(ERROR) << "Interface cannot be added to a static VM.";
return std::nullopt;
}
bool ArcService::StaticGuestIfManager::RemoveInterface(
std::string_view host_ifname) {
LOG(ERROR) << "Interface cannot be removed from a static VM.";
return false;
}
std::optional<std::string> ArcService::StaticGuestIfManager::GetGuestIfName(
std::string_view host_ifname) const {
auto itr = guest_if_names_.find(host_ifname);
if (itr == guest_if_names_.end()) {
return std::nullopt;
} else {
return itr->second;
}
}
std::vector<std::string> ArcService::StaticGuestIfManager::GetStaticTapDevices()
const {
std::vector<std::string> guest_if_name_vec;
for (const auto& map_itr : guest_if_names_) {
guest_if_name_vec.push_back(map_itr.first);
}
return guest_if_name_vec;
}
ArcService::HotplugGuestIfManager::HotplugGuestIfManager(
std::unique_ptr<VmConciergeClient> vm_concierge_client,
std::string_view arc0_tap_ifname,
uint32_t cid)
: arc0_tap_ifname_(arc0_tap_ifname), cid_(cid) {
client_ = std::move(vm_concierge_client);
// eth0 is always occupied by arc0 device, and excluded from hotplug.
guest_if_idx_bitset_.set(0);
client_->RegisterVm(cid);
}
void ArcService::HotplugGuestIfManager::HotplugCallback(
std::string_view tap_ifname, std::optional<uint32_t> bus_num) {
if (!bus_num.has_value()) {
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " to guest failed: concierge error.";
return;
}
// Valid PCI Bus indices are 0-255 inclusive.
if (*bus_num > UINT8_MAX) {
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " to guest failed: invalid bus number " << *bus_num;
return;
}
const uint8_t bus_num_uint8 = uint8_t(*bus_num);
const auto emplace_result =
guest_buses_.try_emplace(std::string(tap_ifname), bus_num_uint8);
if (emplace_result.second) {
LOG(INFO) << "Hotplug host tap " << tap_ifname
<< " to guest succeeded, guest bus: " << bus_num_uint8;
} else {
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " failed: device was already reported inserted at bus "
<< emplace_result.first->second << ", but replaced by "
<< bus_num_uint8;
emplace_result.first->second = bus_num_uint8;
}
}
void ArcService::HotplugGuestIfManager::RemoveCallback(
std::string_view tap_ifname, bool success) {
if (!success) {
LOG(ERROR) << "Remove host tap" << tap_ifname
<< " failed: concierge error.";
return;
}
auto it = guest_buses_.find(tap_ifname);
if (it == guest_buses_.end()) {
LOG(WARNING) << tap_ifname << " is already removed";
} else {
guest_buses_.erase(it);
}
}
std::optional<std::string> ArcService::HotplugGuestIfManager::AddInterface(
std::string_view tap_ifname) {
if (guest_if_idx_.find(tap_ifname) != guest_if_idx_.end()) {
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " failed: tap is already attached to the guest.";
return std::nullopt;
}
// TODO(b/293981114): Change VmConciergeClient to taking string_view directly
// and remove string conversion.
if (!client_->AttachTapDevice(
cid_, std::string(tap_ifname),
base::BindOnce(&HotplugGuestIfManager::HotplugCallback,
base::Unretained(this), tap_ifname))) {
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " failed: cannot make DBus request to concierge.";
return std::nullopt;
}
// The index of the ethernet device is the lowest integer not currently used.
for (size_t i = 0; i < guest_if_idx_bitset_.size(); i++) {
if (!guest_if_idx_bitset_.test(i)) {
guest_if_idx_bitset_.set(i);
guest_if_idx_.emplace(tap_ifname, i);
return kArcVmIfnamePrefix + std::to_string(i);
}
}
LOG(ERROR) << "Hotplug host tap " << tap_ifname
<< " failed: all possible network indices are already taken.";
return std::nullopt;
}
bool ArcService::HotplugGuestIfManager::RemoveInterface(
std::string_view tap_ifname) {
auto idx_itr = guest_if_idx_.find(tap_ifname);
if (idx_itr == guest_if_idx_.end()) {
LOG(ERROR) << "Remove network interface failed: " << tap_ifname
<< " is not found on guest";
return false;
}
auto bus_itr = guest_buses_.find(tap_ifname);
if (bus_itr == guest_buses_.end()) {
LOG(ERROR) << "Remove network interface failed: " << tap_ifname
<< " hotplug failed";
return false;
}
if (!client_->DetachTapDevice(
cid_, bus_itr->second,
base::BindOnce(&HotplugGuestIfManager::RemoveCallback,
base::Unretained(this), tap_ifname))) {
LOG(ERROR) << "Remove network interface failed";
return false;
}
guest_if_idx_bitset_.reset(idx_itr->second);
guest_if_idx_.erase(idx_itr);
return true;
}
std::optional<std::string> ArcService::HotplugGuestIfManager::GetGuestIfName(
std::string_view tap_ifname) const {
auto itr = guest_if_idx_.find(tap_ifname);
if (itr == guest_if_idx_.end()) {
return std::nullopt;
} else {
return kArcVmIfnamePrefix + std::to_string(itr->second);
}
}
std::vector<std::string>
ArcService::HotplugGuestIfManager::GetStaticTapDevices() const {
// For ARCVM with hotplug support, only the arc0 device is always attached.
return {arc0_tap_ifname_};
}
ArcService::ArcService(ArcType arc_type,
Datapath* datapath,
AddressManager* addr_mgr,
ForwardingService* forwarding_service,
MetricsLibraryInterface* metrics,
DbusClientNotifier* dbus_client_notifier)
: arc_type_(arc_type),
datapath_(datapath),
addr_mgr_(addr_mgr),
forwarding_service_(forwarding_service),
metrics_(metrics),
dbus_client_notifier_(dbus_client_notifier),
id_(kInvalidId) {
DCHECK(datapath_);
DCHECK(addr_mgr_);
DCHECK(forwarding_service_);
AllocateArc0Config();
AllocateAddressConfigs();
}
ArcService::~ArcService() {
if (IsStarted()) {
Stop(id_);
}
}
bool ArcService::IsStarted() const {
return id_ != kInvalidId;
}
// Creates the ARC management Device used for VPN forwarding, ADB-over-TCP.
void ArcService::AllocateArc0Config() {
auto ipv4_subnet =
addr_mgr_->AllocateIPv4Subnet(AddressManager::GuestType::kArc0);
if (!ipv4_subnet) {
LOG(ERROR) << __func__ << ": No subnet available";
return;
}
uint32_t subnet_index = (IsVM(arc_type_)) ? 1 : kAnySubnetIndex;
arc0_config_ = std::make_unique<ArcConfig>(
addr_mgr_->GenerateMacAddress(subnet_index), std::move(ipv4_subnet));
all_configs_.push_back(arc0_config_.get());
}
void ArcService::AllocateAddressConfigs() {
// The first usable subnet is the "other" ARC Device subnet.
// As a temporary workaround, for ARCVM, allocate fixed MAC addresses.
uint32_t mac_addr_index = 2;
for (int config_index = 0; config_index < kConfigPoolSize; config_index++) {
auto ipv4_subnet =
addr_mgr_->AllocateIPv4Subnet(AddressManager::GuestType::kArcNet);
if (!ipv4_subnet) {
LOG(ERROR) << __func__ << ": Subnet already in use or unavailable";
continue;
}
const net_base::MacAddress mac_addr =
(arc_type_ == ArcType::kVMStatic)
? addr_mgr_->GenerateMacAddress(mac_addr_index++)
: addr_mgr_->GenerateMacAddress();
const auto& config = available_configs_.emplace_back(
std::make_unique<ArcConfig>(mac_addr, std::move(ipv4_subnet)));
all_configs_.push_back(config.get());
}
}
void ArcService::RefreshMacAddressesInConfigs() {
for (auto* config : all_configs_) {
config->set_mac_addr(addr_mgr_->GenerateMacAddress());
}
}
std::unique_ptr<ArcService::ArcConfig> ArcService::AcquireConfig() {
if (available_configs_.empty()) {
LOG(ERROR) << "Cannot make virtual Device: No more addresses available.";
return nullptr;
}
std::unique_ptr<ArcConfig> config;
config = std::move(available_configs_.back());
available_configs_.pop_back();
return config;
}
void ArcService::ReleaseConfig(std::unique_ptr<ArcConfig> config) {
available_configs_.emplace_back(std::move(config));
}
bool ArcService::StartInternal(
uint32_t id, std::unique_ptr<GuestIfManager> mock_guest_if_manager) {
RecordEvent(metrics_, ArcServiceUmaEvent::kStart);
if (IsStarted()) {
RecordEvent(metrics_, ArcServiceUmaEvent::kStartWithoutStop);
LOG(WARNING) << "Already running - did something crash?"
<< " Stopping and restarting...";
Stop(id_);
}
if (mock_guest_if_manager) {
guest_if_manager_ = std::move(mock_guest_if_manager);
}
std::string arc0_device_ifname;
if (!arc0_config_) {
LOG(ERROR) << "arc0 config not allocated";
return false;
}
switch (arc_type_) {
case ArcType::kContainer: {
pid_t pid = static_cast<int>(id);
if (pid < 0) {
LOG(ERROR) << "Invalid ARC container pid " << pid;
return false;
}
if (!OneTimeContainerSetup(*datapath_, pid)) {
RecordEvent(metrics_, ArcServiceUmaEvent::kOneTimeContainerSetupError);
LOG(ERROR) << "One time container setup failed";
}
if (!datapath_->NetnsAttachName(kArcNetnsName, pid)) {
LOG(ERROR) << "Failed to attach name " << kArcNetnsName << " to pid "
<< pid;
return false;
}
// b/208240700: Refresh MAC address in AddressConfigs every time ARC
// starts to ensure ARC container has different MAC after optout and
// reopt-in.
// TODO(b/185881882): this should be safe to remove after b/185881882.
RefreshMacAddressesInConfigs();
arc0_device_ifname = kVethArc0Ifname;
break;
}
case ArcType::kVMHotplug: {
// Allocate TAP device for arc0 device.
const std::string tap = datapath_->AddTunTap(
/*name=*/"", arc0_config_->mac_addr(),
/*ipv4_cidr=*/std::nullopt, vm_tools::kCrosVmUser, DeviceMode::kTap);
if (tap.empty()) {
LOG(ERROR) << "Failed to create TAP device for arc0";
break;
}
arc0_config_->set_tap_ifname(tap);
arc0_device_ifname = tap;
if (!guest_if_manager_) {
guest_if_manager_ = std::make_unique<HotplugGuestIfManager>(
VmConciergeClientImpl::CreateClientWithNewBus(), arc0_device_ifname,
id);
}
break;
}
case ArcType::kVMStatic: {
// Allocate TAP devices for all configs.
std::vector<std::string> tap_ifnames;
for (auto* config : all_configs_) {
// Tap device name is autogenerated. IPv4 is configured on the bridge.
std::string tap = datapath_->AddTunTap(
/*name=*/"", config->mac_addr(),
/*ipv4_cidr=*/std::nullopt, vm_tools::kCrosVmUser,
DeviceMode::kTap);
if (tap.empty()) {
LOG(ERROR) << "Failed to create TAP device";
continue;
}
config->set_tap_ifname(tap);
tap_ifnames.push_back(std::move(tap));
}
if (!guest_if_manager_) {
guest_if_manager_ = std::make_unique<StaticGuestIfManager>(tap_ifnames);
}
arc0_device_ifname = arc0_config_->tap_ifname();
}
}
id_ = id;
// The "arc0" virtual device is either attached on demand to host VPNs or is
// used to forward host traffic into an Android VPN. Therefore, |shill_device|
// is not meaningful for the "arc0" virtual device and is undefined.
arc0_device_ = ArcDevice(arc_type_, std::nullopt, std::nullopt,
arc0_device_ifname, arc0_config_->mac_addr(),
*arc0_config_, kArcbr0Ifname, kArc0Ifname);
LOG(INFO) << "Starting ARC management Device " << *arc0_device_;
StartArcDeviceDatapath(*arc0_device_);
// Start already known shill <-> ARC mapped devices.
for (const auto& [_, shill_device] : shill_devices_)
AddDevice(shill_device);
// Enable conntrack helpers needed for processing through SNAT the IPv4 GRE
// packets sent by Android PPTP client (b/172214190).
// TODO(b/252749921) Find alternative for chromeos-6.1+ kernels.
if (!datapath_->SetConntrackHelpers(true)) {
// Do not consider this error fatal for ARC datapath setup (b/252749921).
LOG(ERROR) << "Failed to enable conntrack helpers";
}
RecordEvent(metrics_, ArcServiceUmaEvent::kStartSuccess);
return true;
}
bool ArcService::Start(uint32_t id) {
return StartInternal(id, nullptr);
}
bool ArcService::StartWithMockGuestIfManager(
uint32_t id, std::unique_ptr<GuestIfManager> mock_guest_if_manager) {
if (!IsVM(arc_type_)) {
return false;
}
return StartInternal(id, std::move(mock_guest_if_manager));
}
void ArcService::Stop(uint32_t id) {
RecordEvent(metrics_, ArcServiceUmaEvent::kStop);
if (!IsStarted()) {
RecordEvent(metrics_, ArcServiceUmaEvent::kStopBeforeStart);
LOG(ERROR) << "ArcService was not running";
return;
}
// After the ARC container has stopped, the pid is not known anymore.
// The stop message for ARCVM may be sent after a new VM is started. Only
// stop if the CID matched the latest started ARCVM CID.
if (IsVM(arc_type_) && id_ != id) {
LOG(WARNING) << "Mismatched ARCVM CIDs " << id_ << " != " << id;
return;
}
if (!datapath_->SetConntrackHelpers(false))
LOG(ERROR) << "Failed to disable conntrack helpers";
// Remove all ARC Devices associated with a shill Device.
// Make a copy of |shill_devices_| to avoid invalidating any iterator over
// |shill_devices_| while removing device from it and resetting it afterwards.
auto shill_devices = shill_devices_;
for (const auto& [_, shill_device] : shill_devices) {
RemoveDevice(shill_device);
}
shill_devices_ = shill_devices;
StopArcDeviceDatapath(*arc0_device_);
LOG(INFO) << "Stopped ARC management Device " << *arc0_device_;
arc0_device_ = std::nullopt;
if (IsVM(arc_type_)) {
guest_if_manager_.reset();
for (auto* config : all_configs_) {
if (config->tap_ifname().empty()) {
continue;
}
datapath_->RemoveInterface(config->tap_ifname());
config->set_tap_ifname("");
}
} else {
// Free the network namespace name attached to the ARC container.
if (!datapath_->NetnsDeleteName(kArcNetnsName)) {
LOG(ERROR) << "Failed to delete netns name " << kArcNetnsName;
}
}
id_ = kInvalidId;
is_arc_interactive_ = true;
is_android_wifi_multicast_lock_held_ = false;
RecordEvent(metrics_, ArcServiceUmaEvent::kStopSuccess);
}
void ArcService::AddDevice(const ShillClient::Device& shill_device) {
shill_devices_[shill_device.shill_device_interface_property] = shill_device;
if (!IsStarted())
return;
if (shill_device.ifname.empty())
return;
RecordEvent(metrics_, ArcServiceUmaEvent::kAddDevice);
if (devices_.find(shill_device.ifname) != devices_.end()) {
LOG(DFATAL) << "Attemping to add already tracked shill device "
<< shill_device;
return;
}
// TODO(b:323291863): Fix config leak when AddDevice fails.
auto config = AcquireConfig();
if (!config) {
LOG(ERROR) << "Cannot acquire an ARC IPv4 config for shill device "
<< shill_device;
return;
}
if (arc_type_ == ArcType::kVMHotplug) {
const std::string tap_ifname = datapath_->AddTunTap(
/*name=*/"", config->mac_addr(),
/*ipv4_cidr=*/std::nullopt, vm_tools::kCrosVmUser, DeviceMode::kTap);
if (tap_ifname.empty()) {
LOG(ERROR) << "Failed to create tap device for shill device "
<< shill_device;
ReleaseConfig(std::move(config));
return;
}
if (!guest_if_manager_->AddInterface(tap_ifname).has_value()) {
LOG(ERROR) << "Failed to hotplug tap device " << tap_ifname
<< " to guest for shill device " << shill_device;
ReleaseConfig(std::move(config));
return;
}
config->set_tap_ifname(tap_ifname);
}
// The interface name visible inside ARC depends on the type of ARC
// environment:
// - ARC container: the veth interface created inside ARC has the same name
// as the shill Device that this ARC virtual device is attached to.
// b/273741099: For Cellular multiplexed interfaces, the name of the shill
// Device is used such that the rest of the ARC stack does not need to be
// aware of Cellular multiplexing.
// - ARCVM: |guest_if_manager_| tracks the name of guest interfaces.
std::string arc_device_ifname;
std::string guest_ifname;
if (IsVM(arc_type_)) {
arc_device_ifname = config->tap_ifname();
if (arc_device_ifname.empty()) {
LOG(ERROR) << "No TAP device for " << shill_device;
ReleaseConfig(std::move(config));
return;
}
const auto guest_ifname_opt =
guest_if_manager_->GetGuestIfName(config->tap_ifname());
if (!guest_ifname_opt.has_value()) {
LOG(ERROR) << "No guest device for " << shill_device;
ReleaseConfig(std::move(config));
return;
}
guest_ifname = *guest_ifname_opt;
} else { // arc_type_ == kContainer
arc_device_ifname = ArcVethHostName(shill_device);
guest_ifname = shill_device.shill_device_interface_property;
}
if (!IsArcValidTechnology(shill_device.technology)) {
LOG(ERROR) << "Shill device technology type "
<< (shill_device.technology.has_value()
? net_base::ToString(*shill_device.technology)
: "unknown")
<< " is invalid for ArcDevice.";
ReleaseConfig(std::move(config));
return;
}
auto arc_device_it = devices_.try_emplace(
shill_device.ifname, arc_type_, *shill_device.technology,
shill_device.shill_device_interface_property, arc_device_ifname,
config->mac_addr(), *config, ArcBridgeName(shill_device), guest_ifname);
LOG(INFO) << "Starting ARC Device " << arc_device_it.first->second;
StartArcDeviceDatapath(arc_device_it.first->second);
forwarding_service_->StartIPv6NDPForwarding(
shill_device, arc_device_it.first->second.bridge_ifname());
// Only start forwarding multicast inbound traffic if ARC is in an
// interactive state. In addition, on WiFi the Android WiFi multicast lock
// must also be held. Multicast forwarding is not supported for WiFi Direct
// client Networks started by Android App requests.
// Outbound multicast traffic is always allowed.
bool forward_inbound =
is_arc_interactive_ &&
(shill_device.technology != net_base::Technology::kWiFi ||
is_android_wifi_multicast_lock_held_);
auto dir = forward_inbound ? MulticastForwarder::Direction::kTwoWays
: MulticastForwarder::Direction::kOutboundOnly;
forwarding_service_->StartMulticastForwarding(
shill_device, arc_device_it.first->second.bridge_ifname(), dir);
if ((shill_device.technology == net_base::Technology::kWiFi) ||
(shill_device.technology == net_base::Technology::kEthernet)) {
forwarding_service_->StartBroadcastForwarding(
shill_device, arc_device_it.first->second.bridge_ifname());
}
auto signal_device = std::make_unique<NetworkDevice>();
arc_device_it.first->second.ConvertToProto(signal_device.get());
dbus_client_notifier_->OnNetworkDeviceChanged(
std::move(signal_device), NetworkDeviceChangedSignal::DEVICE_ADDED);
assigned_configs_.emplace(shill_device.ifname, std::move(config));
RecordEvent(metrics_, ArcServiceUmaEvent::kAddDeviceSuccess);
}
void ArcService::RemoveDevice(const ShillClient::Device& shill_device) {
if (IsStarted()) {
const auto it = devices_.find(shill_device.ifname);
if (it == devices_.end()) {
LOG(WARNING) << "Unknown shill Device " << shill_device;
} else {
const auto& arc_device = it->second;
LOG(INFO) << "Removing ARC Device " << arc_device;
if (arc_type_ == ArcType::kVMHotplug) {
guest_if_manager_->RemoveInterface(arc_device.arc_device_ifname());
}
auto signal_device = std::make_unique<NetworkDevice>();
arc_device.ConvertToProto(signal_device.get());
dbus_client_notifier_->OnNetworkDeviceChanged(
std::move(signal_device), NetworkDeviceChangedSignal::DEVICE_REMOVED);
forwarding_service_->StopIPv6NDPForwarding(shill_device,
arc_device.bridge_ifname());
forwarding_service_->StopMulticastForwarding(
shill_device, arc_device.bridge_ifname(),
MulticastForwarder::Direction::kTwoWays);
forwarding_service_->StopBroadcastForwarding(shill_device,
arc_device.bridge_ifname());
StopArcDeviceDatapath(arc_device);
auto config_it = assigned_configs_.find(shill_device.ifname);
if (config_it == assigned_configs_.end()) {
LOG(ERROR) << "No IPv4 configuration found for ARC Device "
<< arc_device;
} else {
if (arc_type_ == ArcType::kVMHotplug) {
datapath_->RemoveTunTap(config_it->second->tap_ifname(),
DeviceMode::kTap);
config_it->second->set_tap_ifname("");
}
ReleaseConfig(std::move(config_it->second));
assigned_configs_.erase(config_it);
}
devices_.erase(it);
}
}
shill_devices_.erase(shill_device.shill_device_interface_property);
}
void ArcService::UpdateDeviceIPConfig(const ShillClient::Device& shill_device) {
auto shill_device_it =
shill_devices_.find(shill_device.shill_device_interface_property);
if (shill_device_it == shill_devices_.end()) {
LOG(WARNING) << "Unknown shill Device " << shill_device;
return;
}
shill_device_it->second = shill_device;
}
std::optional<net_base::IPv4Address> ArcService::GetArc0IPv4Address() const {
if (!arc0_config_) {
return std::nullopt;
}
return arc0_config_->arc_ipv4_address().address();
}
std::vector<std::string> ArcService::GetStaticTapDevices() const {
if (IsVM(arc_type_)) {
return guest_if_manager_->GetStaticTapDevices();
}
return {};
}
std::vector<const ArcService::ArcDevice*> ArcService::GetDevices() const {
std::vector<const ArcDevice*> devices;
for (const auto& [_, dev] : devices_) {
devices.push_back(&dev);
}
return devices;
}
// static
std::string ArcService::ArcVethHostName(const ShillClient::Device& device) {
return PrefixIfname("veth", device.shill_device_interface_property);
}
// static
std::string ArcService::ArcBridgeName(const ShillClient::Device& device) {
return PrefixIfname("arc_", device.shill_device_interface_property);
}
std::ostream& operator<<(std::ostream& stream,
const ArcService::ArcDevice& arc_device) {
stream << "{ type: " << arc_device.type()
<< ", arc_device_ifname: " << arc_device.arc_device_ifname()
<< ", arc_ipv4_addr: " << arc_device.arc_ipv4_address()
<< ", arc_device_mac_addr: "
<< arc_device.arc_device_mac_address().ToString()
<< ", bridge_ifname: " << arc_device.bridge_ifname()
<< ", bridge_ipv4_addr: " << arc_device.bridge_ipv4_address()
<< ", guest_device_ifname: " << arc_device.guest_device_ifname();
if (arc_device.shill_device_ifname()) {
stream << ", shill_ifname: " << *arc_device.shill_device_ifname();
}
return stream << '}';
}
std::ostream& operator<<(std::ostream& stream, ArcService::ArcType arc_type) {
switch (arc_type) {
case ArcService::ArcType::kContainer:
return stream << "ARC Container";
case ArcService::ArcType::kVMStatic:
return stream << "ARCVM";
case ArcService::ArcType::kVMHotplug:
return stream << "ARCVM with hotplug support";
}
}
void ArcService::StartArcDeviceDatapath(
const ArcService::ArcDevice& arc_device) {
// Only create the host virtual interface and guest virtual interface for
// the container. The TAP devices are currently always created statically
// ahead of time.
if (arc_type_ == ArcType::kContainer) {
pid_t pid = static_cast<int>(id_);
if (pid < 0) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Invalid ARC container pid " << pid;
return;
}
// ARC requires multicast capability at all times. This is tested as part of
// CTS and CDD.
if (!datapath_->ConnectVethPair(
pid, kArcNetnsName, arc_device.arc_device_ifname(),
arc_device.guest_device_ifname(),
arc_device.arc_device_mac_address(), arc_device.arc_ipv4_address(),
/*remote_ipv6_cidr=*/std::nullopt,
/*remote_multicast_flag=*/true)) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Cannot create virtual ethernet pair";
return;
}
// Allow netd to write to /sys/class/net/arc0/mtu (b/175571457).
if (!SetSysfsOwnerToAndroidRoot(
pid,
base::StrCat({"net/", arc_device.guest_device_ifname(), "/mtu"}))) {
RecordEvent(metrics_, ArcServiceUmaEvent::kSetVethMtuError);
}
}
// Create the associated bridge and link the host virtual device to the
// bridge.
if (!datapath_->AddBridge(arc_device.bridge_ifname(),
arc_device.bridge_ipv4_address())) {
LOG(ERROR) << __func__ << "(" << arc_device << "): Failed to setup bridge";
return;
}
if (!datapath_->AddToBridge(arc_device.bridge_ifname(),
arc_device.arc_device_ifname())) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Failed to link bridge and ARC virtual interface";
return;
}
if (!arc_device.shill_device_ifname()) {
return;
}
// Only setup additional iptables rules for ARC Devices bound to a shill
// Device. The iptables rules for arc0 are configured only when a VPN
// connection exists and are triggered directly from Manager when the default
// logical network switches to a VPN.
const auto shill_device_it =
shill_devices_.find(*arc_device.shill_device_ifname());
if (shill_device_it == shill_devices_.end()) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Failed to find shill Device";
return;
}
datapath_->StartRoutingDevice(
shill_device_it->second, arc_device.bridge_ifname(), TrafficSource::kArc);
datapath_->AddInboundIPv4DNAT(AutoDNATTarget::kArc, shill_device_it->second,
arc_device.arc_ipv4_address().address());
if (IsAdbAllowed(shill_device_it->second.technology) &&
!datapath_->AddAdbPortAccessRule(shill_device_it->second.ifname)) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Failed to add ADB port access rule";
}
}
void ArcService::StopArcDeviceDatapath(
const ArcService::ArcDevice& arc_device) {
if (arc_device.shill_device_ifname()) {
const auto shill_device_it =
shill_devices_.find(*arc_device.shill_device_ifname());
if (shill_device_it == shill_devices_.end()) {
LOG(ERROR) << __func__ << "(" << arc_device
<< "): Failed to find shill Device";
} else {
if (IsAdbAllowed(shill_device_it->second.technology)) {
datapath_->DeleteAdbPortAccessRule(shill_device_it->second.ifname);
}
datapath_->RemoveInboundIPv4DNAT(AutoDNATTarget::kArc,
shill_device_it->second,
arc_device.arc_ipv4_address().address());
datapath_->StopRoutingDevice(arc_device.bridge_ifname(),
TrafficSource::kArc);
}
}
datapath_->RemoveBridge(arc_device.bridge_ifname());
// Only destroy the host virtual interface for the container. ARCVM TAP
// devices are removed separately when ARC stops.
if (arc_type_ == ArcType::kContainer) {
datapath_->RemoveInterface(arc_device.arc_device_ifname());
}
}
void ArcService::NotifyAndroidWifiMulticastLockChange(bool is_held) {
if (!IsStarted()) {
return;
}
// When multicast lock status changes from not held to held or the other
// way, decide whether to enable or disable multicast forwarder for ARC.
if (is_android_wifi_multicast_lock_held_ == is_held) {
return;
}
is_android_wifi_multicast_lock_held_ = is_held;
// If arc is not interactive, multicast lock held status does not
// affect multicast traffic.
if (!is_arc_interactive_) {
return;
}
// Only start/stop multicast forwarding when multicast allowed status changes
// to avoid start/stop multicast forwarding multiple times, also wifi
// multicast lock should only affect inbound multicast traffic on wireless
// device. Note that this change only affects inbound multicast forwarding.
// Outbound multicast traffic and broadcast forwarding state is unchanged
// during the process.
for (const auto& [shill_device_ifname, arc_device] : devices_) {
const auto shill_device_it = shill_devices_.find(shill_device_ifname);
if (shill_device_it == shill_devices_.end()) {
LOG(ERROR) << __func__
<< ": no upstream shill Device found for ARC Device "
<< arc_device;
continue;
}
if (shill_device_it->second.technology != net_base::Technology::kWiFi) {
continue;
}
if (is_android_wifi_multicast_lock_held_) {
forwarding_service_->StartMulticastForwarding(
shill_device_it->second, arc_device.bridge_ifname(),
MulticastForwarder::Direction::kInboundOnly);
} else {
forwarding_service_->StopMulticastForwarding(
shill_device_it->second, arc_device.bridge_ifname(),
MulticastForwarder::Direction::kInboundOnly);
}
}
}
void ArcService::NotifyAndroidInteractiveState(bool is_interactive) {
if (!IsStarted()) {
return;
}
if (is_arc_interactive_ == is_interactive) {
return;
}
is_arc_interactive_ = is_interactive;
// If ARC power state has changed to interactive, starts multicast forwarding
// for inbound traffic for all non-WiFi interfaces and for WiFi interfaces
// when WiFi multicast lock is held.
// If ARC power state has changed to non-interactive, disable inbound
// multicast forwarding for all interfaces.
// Note that this change only affects multicast forwarding and broadcast
// forwarding state is unchanged during the process.
for (const auto& [shill_device_ifname, arc_device] : devices_) {
const auto shill_device_it = shill_devices_.find(shill_device_ifname);
if (shill_device_it == shill_devices_.end()) {
LOG(ERROR) << __func__
<< ": no upstream shill Device found for ARC Device "
<< arc_device;
continue;
}
if (shill_device_it->second.technology == net_base::Technology::kWiFi &&
!is_android_wifi_multicast_lock_held_) {
continue;
}
if (is_arc_interactive_) {
forwarding_service_->StartMulticastForwarding(
shill_device_it->second, arc_device.bridge_ifname(),
MulticastForwarder::Direction::kInboundOnly);
} else {
forwarding_service_->StopMulticastForwarding(
shill_device_it->second, arc_device.bridge_ifname(),
MulticastForwarder::Direction::kInboundOnly);
}
}
}
bool ArcService::IsWiFiMulticastForwardingRunning() {
// Check multicast forwarding conditions for WiFi. This implies ARC is
// running.
if (!is_arc_interactive_ || !is_android_wifi_multicast_lock_held_) {
return false;
}
// Ensure there is also an active WiFi Device;
for (const auto& [_, shill_dev] : shill_devices_) {
if (shill_dev.technology == net_base::Technology::kWiFi) {
return true;
}
}
return false;
}
} // namespace patchpanel