| // 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 |