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